From d8cc8daef993131777c8ba13d69ca116cad931e1 Mon Sep 17 00:00:00 2001 From: Greg Alexander Date: Sun, 21 Dec 2014 17:56:03 -0500 Subject: [PATCH] rsync 3.1.1 source, after i trimmed away the parts we obviously won't need...but most of it remains. --- rsync/COPYING | 674 +++++++ rsync/Doxyfile | 187 ++ rsync/INSTALL | 74 + rsync/Makefile.in | 323 +++ rsync/NEWS | 115 ++ rsync/OLDNEWS | 3599 ++++++++++++++++++++++++++++++++++ rsync/README | 140 ++ rsync/TODO | 528 +++++ rsync/access.c | 290 +++ rsync/aclocal.m4 | 92 + rsync/acls.c | 1155 +++++++++++ rsync/authenticate.c | 373 ++++ rsync/backup.c | 341 ++++ rsync/batch.c | 283 +++ rsync/byteorder.h | 124 ++ rsync/case_N.h | 76 + rsync/checksum.c | 223 +++ rsync/chmod.c | 249 +++ rsync/cleanup.c | 292 +++ rsync/clientname.c | 348 ++++ rsync/clientserver.c | 1204 ++++++++++++ rsync/compat.c | 336 ++++ rsync/config.h.in | 769 ++++++++ rsync/connection.c | 46 + rsync/csprotocol.txt | 88 + rsync/delete.c | 240 +++ rsync/doc/README-SGML | 20 + rsync/doc/profile.txt | 42 + rsync/doc/rsync.sgml | 351 ++++ rsync/errcode.h | 64 + rsync/exclude.c | 1401 +++++++++++++ rsync/fileio.c | 288 +++ rsync/flist.c | 3231 ++++++++++++++++++++++++++++++ rsync/generator.c | 2370 ++++++++++++++++++++++ rsync/getfsdev.c | 23 + rsync/getgroups.c | 62 + rsync/hashtable.c | 172 ++ rsync/hlink.c | 571 ++++++ rsync/ifuncs.h | 106 + rsync/install-sh | 238 +++ rsync/inums.h | 57 + rsync/io.c | 2384 ++++++++++++++++++++++ rsync/io.h | 52 + rsync/itypes.h | 59 + rsync/lib/addrinfo.h | 180 ++ rsync/lib/compat.c | 275 +++ rsync/lib/dummy.in | 2 + rsync/lib/getaddrinfo.c | 504 +++++ rsync/lib/getpass.c | 72 + rsync/lib/inet_ntop.c | 186 ++ rsync/lib/inet_pton.c | 212 ++ rsync/lib/md5.c | 304 +++ rsync/lib/mdfour.c | 246 +++ rsync/lib/mdigest.h | 26 + rsync/lib/permstring.c | 65 + rsync/lib/permstring.h | 3 + rsync/lib/pool_alloc.3 | 268 +++ rsync/lib/pool_alloc.c | 376 ++++ rsync/lib/pool_alloc.h | 21 + rsync/lib/snprintf.c | 1512 ++++++++++++++ rsync/lib/sysacls.c | 2796 ++++++++++++++++++++++++++ rsync/lib/sysacls.h | 305 +++ rsync/lib/sysxattrs.c | 300 +++ rsync/lib/sysxattrs.h | 26 + rsync/lib/wildmatch.c | 368 ++++ rsync/lib/wildmatch.h | 6 + rsync/loadparm.c | 846 ++++++++ rsync/log.c | 896 +++++++++ rsync/main.c | 1640 ++++++++++++++++ rsync/match.c | 448 +++++ rsync/mkproto.pl | 48 + rsync/options.c | 2880 +++++++++++++++++++++++++++ rsync/params.c | 666 +++++++ rsync/pipe.c | 180 ++ rsync/popt/CHANGES | 46 + rsync/popt/COPYING | 22 + rsync/popt/README | 18 + rsync/popt/README.rsync | 4 + rsync/popt/dummy.in | 0 rsync/popt/findme.c | 55 + rsync/popt/findme.h | 20 + rsync/popt/popt.c | 1283 ++++++++++++ rsync/popt/popt.h | 565 ++++++ rsync/popt/poptconfig.c | 183 ++ rsync/popt/popthelp.c | 826 ++++++++ rsync/popt/poptint.h | 122 ++ rsync/popt/poptparse.c | 231 +++ rsync/popt/system.h | 130 ++ rsync/prepare-source | 51 + rsync/prepare-source.mak | 7 + rsync/progress.c | 221 +++ rsync/proto.h | 423 ++++ rsync/proto.h-tstamp | 0 rsync/receiver.c | 953 +++++++++ rsync/rounding.c | 38 + rsync/rsync-3.1.1/.gitignore | 46 + rsync/rsync-ssl.in | 12 + rsync/rsync.c | 747 +++++++ rsync/rsync.h | 1294 ++++++++++++ rsync/rsync3.txt | 469 +++++ rsync/rsyncd.conf.5 | 1082 ++++++++++ rsync/rsyncd.conf.yo | 934 +++++++++ rsync/rsyncsh.txt | 26 + rsync/runtests.sh | 334 ++++ rsync/sender.c | 430 ++++ rsync/shconfig.in | 15 + rsync/socket.c | 855 ++++++++ rsync/stunnel-rsync.in | 52 + rsync/stunnel-rsyncd.conf.in | 30 + rsync/syscall.c | 491 +++++ rsync/t_stub.c | 99 + rsync/t_unsafe.c | 48 + rsync/tech_report.tex | 310 +++ rsync/testhelp/maketree.py | 133 ++ rsync/testrun.c | 61 + rsync/tls.c | 261 +++ rsync/token.c | 666 +++++++ rsync/trimslash.c | 47 + rsync/tweak_manpage | 28 + rsync/uidlist.c | 548 ++++++ rsync/util.c | 1635 +++++++++++++++ rsync/util2.c | 109 + rsync/wildtest.c | 221 +++ rsync/wildtest.txt | 165 ++ rsync/xattrs.c | 1108 +++++++++++ 125 files changed, 56771 insertions(+) create mode 100644 rsync/COPYING create mode 100644 rsync/Doxyfile create mode 100644 rsync/INSTALL create mode 100644 rsync/Makefile.in create mode 100644 rsync/NEWS create mode 100644 rsync/OLDNEWS create mode 100644 rsync/README create mode 100644 rsync/TODO create mode 100644 rsync/access.c create mode 100644 rsync/aclocal.m4 create mode 100644 rsync/acls.c create mode 100644 rsync/authenticate.c create mode 100644 rsync/backup.c create mode 100644 rsync/batch.c create mode 100644 rsync/byteorder.h create mode 100644 rsync/case_N.h create mode 100644 rsync/checksum.c create mode 100644 rsync/chmod.c create mode 100644 rsync/cleanup.c create mode 100644 rsync/clientname.c create mode 100644 rsync/clientserver.c create mode 100644 rsync/compat.c create mode 100644 rsync/config.h.in create mode 100644 rsync/connection.c create mode 100644 rsync/csprotocol.txt create mode 100644 rsync/delete.c create mode 100644 rsync/doc/README-SGML create mode 100644 rsync/doc/profile.txt create mode 100644 rsync/doc/rsync.sgml create mode 100644 rsync/errcode.h create mode 100644 rsync/exclude.c create mode 100644 rsync/fileio.c create mode 100644 rsync/flist.c create mode 100644 rsync/generator.c create mode 100644 rsync/getfsdev.c create mode 100644 rsync/getgroups.c create mode 100644 rsync/hashtable.c create mode 100644 rsync/hlink.c create mode 100644 rsync/ifuncs.h create mode 100755 rsync/install-sh create mode 100644 rsync/inums.h create mode 100644 rsync/io.c create mode 100644 rsync/io.h create mode 100644 rsync/itypes.h create mode 100644 rsync/lib/addrinfo.h create mode 100644 rsync/lib/compat.c create mode 100644 rsync/lib/dummy.in create mode 100644 rsync/lib/getaddrinfo.c create mode 100644 rsync/lib/getpass.c create mode 100644 rsync/lib/inet_ntop.c create mode 100644 rsync/lib/inet_pton.c create mode 100644 rsync/lib/md5.c create mode 100644 rsync/lib/mdfour.c create mode 100644 rsync/lib/mdigest.h create mode 100644 rsync/lib/permstring.c create mode 100644 rsync/lib/permstring.h create mode 100644 rsync/lib/pool_alloc.3 create mode 100644 rsync/lib/pool_alloc.c create mode 100644 rsync/lib/pool_alloc.h create mode 100644 rsync/lib/snprintf.c create mode 100644 rsync/lib/sysacls.c create mode 100644 rsync/lib/sysacls.h create mode 100644 rsync/lib/sysxattrs.c create mode 100644 rsync/lib/sysxattrs.h create mode 100644 rsync/lib/wildmatch.c create mode 100644 rsync/lib/wildmatch.h create mode 100644 rsync/loadparm.c create mode 100644 rsync/log.c create mode 100644 rsync/main.c create mode 100644 rsync/match.c create mode 100644 rsync/mkproto.pl create mode 100644 rsync/options.c create mode 100644 rsync/params.c create mode 100644 rsync/pipe.c create mode 100644 rsync/popt/CHANGES create mode 100644 rsync/popt/COPYING create mode 100644 rsync/popt/README create mode 100644 rsync/popt/README.rsync create mode 100644 rsync/popt/dummy.in create mode 100644 rsync/popt/findme.c create mode 100644 rsync/popt/findme.h create mode 100644 rsync/popt/popt.c create mode 100644 rsync/popt/popt.h create mode 100644 rsync/popt/poptconfig.c create mode 100644 rsync/popt/popthelp.c create mode 100644 rsync/popt/poptint.h create mode 100644 rsync/popt/poptparse.c create mode 100644 rsync/popt/system.h create mode 100755 rsync/prepare-source create mode 100644 rsync/prepare-source.mak create mode 100644 rsync/progress.c create mode 100644 rsync/proto.h create mode 100644 rsync/proto.h-tstamp create mode 100644 rsync/receiver.c create mode 100644 rsync/rounding.c create mode 100644 rsync/rsync-3.1.1/.gitignore create mode 100755 rsync/rsync-ssl.in create mode 100644 rsync/rsync.c create mode 100644 rsync/rsync.h create mode 100644 rsync/rsync3.txt create mode 100644 rsync/rsyncd.conf.5 create mode 100644 rsync/rsyncd.conf.yo create mode 100644 rsync/rsyncsh.txt create mode 100755 rsync/runtests.sh create mode 100644 rsync/sender.c create mode 100755 rsync/shconfig.in create mode 100644 rsync/socket.c create mode 100755 rsync/stunnel-rsync.in create mode 100644 rsync/stunnel-rsyncd.conf.in create mode 100644 rsync/syscall.c create mode 100644 rsync/t_stub.c create mode 100644 rsync/t_unsafe.c create mode 100644 rsync/tech_report.tex create mode 100644 rsync/testhelp/maketree.py create mode 100644 rsync/testrun.c create mode 100644 rsync/tls.c create mode 100644 rsync/token.c create mode 100644 rsync/trimslash.c create mode 100755 rsync/tweak_manpage create mode 100644 rsync/uidlist.c create mode 100644 rsync/util.c create mode 100644 rsync/util2.c create mode 100644 rsync/wildtest.c create mode 100644 rsync/wildtest.txt create mode 100644 rsync/xattrs.c diff --git a/rsync/COPYING b/rsync/COPYING new file mode 100644 index 0000000..94a9ed0 --- /dev/null +++ b/rsync/COPYING @@ -0,0 +1,674 @@ + 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/rsync/Doxyfile b/rsync/Doxyfile new file mode 100644 index 0000000..14e8d61 --- /dev/null +++ b/rsync/Doxyfile @@ -0,0 +1,187 @@ +# Doxyfile 1.2.15 + +#--------------------------------------------------------------------------- +# General configuration options +#--------------------------------------------------------------------------- +PROJECT_NAME = rsync +PROJECT_NUMBER = HEAD +OUTPUT_DIRECTORY = dox +OUTPUT_LANGUAGE = English +EXTRACT_ALL = YES +EXTRACT_PRIVATE = YES +EXTRACT_STATIC = YES +EXTRACT_LOCAL_CLASSES = YES +HIDE_UNDOC_MEMBERS = NO +HIDE_UNDOC_CLASSES = NO +BRIEF_MEMBER_DESC = YES +REPEAT_BRIEF = YES +ALWAYS_DETAILED_SEC = NO +INLINE_INHERITED_MEMB = NO +FULL_PATH_NAMES = NO +STRIP_FROM_PATH = *source +INTERNAL_DOCS = YES +STRIP_CODE_COMMENTS = NO +CASE_SENSE_NAMES = YES +SHORT_NAMES = NO +HIDE_SCOPE_NAMES = YES +VERBATIM_HEADERS = YES +SHOW_INCLUDE_FILES = YES +JAVADOC_AUTOBRIEF = YES +INHERIT_DOCS = YES +INLINE_INFO = YES +SORT_MEMBER_DOCS = NO +DISTRIBUTE_GROUP_DOC = NO +TAB_SIZE = 8 +GENERATE_TODOLIST = YES +GENERATE_TESTLIST = YES +GENERATE_BUGLIST = YES +ALIASES = +ENABLED_SECTIONS = +MAX_INITIALIZER_LINES = 30 +OPTIMIZE_OUTPUT_FOR_C = YES +OPTIMIZE_OUTPUT_JAVA = NO +SHOW_USED_FILES = YES +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- +QUIET = NO +WARNINGS = NO +WARN_IF_UNDOCUMENTED = NO +WARN_FORMAT = "$file:$line: $text" +WARN_LOGFILE = +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- +INPUT = . +FILE_PATTERNS = *.c \ + *.h +RECURSIVE = YES +EXCLUDE = proto.h \ + zlib \ + popt +EXCLUDE_SYMLINKS = NO +EXCLUDE_PATTERNS = +EXAMPLE_PATH = +EXAMPLE_PATTERNS = +EXAMPLE_RECURSIVE = NO +IMAGE_PATH = +INPUT_FILTER = +FILTER_SOURCE_FILES = NO +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- +SOURCE_BROWSER = YES +INLINE_SOURCES = YES +REFERENCED_BY_RELATION = YES +REFERENCES_RELATION = YES +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- +ALPHABETICAL_INDEX = YES +COLS_IN_ALPHA_INDEX = 3 +IGNORE_PREFIX = +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- +GENERATE_HTML = YES +HTML_OUTPUT = html +HTML_FILE_EXTENSION = .html +HTML_HEADER = +HTML_FOOTER = +HTML_STYLESHEET = +HTML_ALIGN_MEMBERS = YES +GENERATE_HTMLHELP = NO +GENERATE_CHI = NO +BINARY_TOC = NO +TOC_EXPAND = NO +DISABLE_INDEX = NO +ENUM_VALUES_PER_LINE = 3 +GENERATE_TREEVIEW = NO +TREEVIEW_WIDTH = 250 +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- +GENERATE_LATEX = NO +LATEX_OUTPUT = latex +LATEX_CMD_NAME = latex +MAKEINDEX_CMD_NAME = makeindex +COMPACT_LATEX = NO +PAPER_TYPE = a4wide +EXTRA_PACKAGES = +LATEX_HEADER = +PDF_HYPERLINKS = YES +USE_PDFLATEX = YES +LATEX_BATCHMODE = YES +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- +GENERATE_RTF = NO +RTF_OUTPUT = rtf +COMPACT_RTF = NO +RTF_HYPERLINKS = NO +RTF_STYLESHEET_FILE = +RTF_EXTENSIONS_FILE = +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- +GENERATE_MAN = NO +MAN_OUTPUT = man +MAN_EXTENSION = .3 +MAN_LINKS = NO +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- +GENERATE_XML = NO +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- +GENERATE_AUTOGEN_DEF = NO +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- +ENABLE_PREPROCESSING = NO +MACRO_EXPANSION = NO +EXPAND_ONLY_PREDEF = NO +SEARCH_INCLUDES = YES +INCLUDE_PATH = +INCLUDE_FILE_PATTERNS = +PREDEFINED = +EXPAND_AS_DEFINED = +SKIP_FUNCTION_MACROS = YES +#--------------------------------------------------------------------------- +# Configuration::addtions related to external references +#--------------------------------------------------------------------------- +TAGFILES = +GENERATE_TAGFILE = +ALLEXTERNALS = NO +EXTERNAL_GROUPS = YES +PERL_PATH = /usr/bin/perl +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- +CLASS_DIAGRAMS = YES +HAVE_DOT = YES +CLASS_GRAPH = YES +COLLABORATION_GRAPH = YES +TEMPLATE_RELATIONS = YES +HIDE_UNDOC_RELATIONS = YES +INCLUDE_GRAPH = YES +INCLUDED_BY_GRAPH = YES +GRAPHICAL_HIERARCHY = YES +DOT_IMAGE_FORMAT = png +DOT_PATH = +DOTFILE_DIRS = +MAX_DOT_GRAPH_WIDTH = 1024 +MAX_DOT_GRAPH_HEIGHT = 1024 +GENERATE_LEGEND = YES +DOT_CLEANUP = YES +#--------------------------------------------------------------------------- +# Configuration::addtions related to the search engine +#--------------------------------------------------------------------------- +SEARCHENGINE = NO +CGI_NAME = search.cgi +CGI_URL = +DOC_URL = +DOC_ABSPATH = +BIN_ABSPATH = /usr/local/bin/ +EXT_DOC_PATHS = diff --git a/rsync/INSTALL b/rsync/INSTALL new file mode 100644 index 0000000..6c016ad --- /dev/null +++ b/rsync/INSTALL @@ -0,0 +1,74 @@ +To build and install rsync: + + $ ./configure + $ make + # make install + +You may set the installation directory and other parameters by options +to ./configure. To see them, use: + + $ ./configure --help + +Configure tries to figure out if the local system uses group "nobody" or +"nogroup" by looking in the /etc/group file. (This is only used for the +default group of an rsync daemon, which attempts to run with "nobody" +user and group permissions.) You can change the default user and group +for the daemon by editing the NOBODY_USER and NOBODY_GROUP defines in +config.h, or just override them in your /etc/rsyncd.conf file. + +As of 2.4.7, rsync uses Eric Troan's popt option-parsing library. A +cut-down copy of a recent release is included in the rsync distribution, +and will be used if there is no popt library on your build host, or if +the --with-included-popt option is passed to ./configure. + +If you configure using --enable-maintainer-mode, then rsync will try +to pop up an xterm on DISPLAY=:0 if it crashes. You might find this +useful, but it should be turned off for production builds. + +MAKE COMPATIBILITY +------------------ + +Note that Makefile.in has a rule that uses a wildcard in a prerequisite. If +your make has a problem with this rule, you will see an error like this: + + Don't know how to make ./*.c + +You can change the "proto.h-tstamp" target in Makefile.in to list all the *.c +filenames explicitly in order to avoid this issue. + +RPM NOTES +--------- + +Under packaging you will find .spec files for several distributions. +The .spec file in packaging/lsb can be used for Linux systems that +adhere to the Linux Standards Base (e.g., RedHat and others). + +HP-UX NOTES +----------- + +The HP-UX 10.10 "bundled" C compiler seems not to be able to cope with +ANSI C. You may see this error message in config.log if ./configure +fails: + + (Bundled) cc: "configure", line 2162: error 1705: Function prototypes are an ANSI feature. + +Install gcc or HP's "ANSI/C Compiler". + +MAC OSX NOTES +------------- + +Some versions of Mac OS X (Darwin) seem to have an IPv6 stack, but do +not completely implement the "New Sockets" API. + + says that Apple started to support +IPv6 in 10.2 (Jaguar). If your build fails, try again after running +configure with --disable-ipv6. + +IBM AIX NOTES +------------- + +IBM AIX has a largefile problem with mkstemp. See IBM PR-51921. +The workaround is to append the below to config.h + #ifdef _LARGE_FILES + #undef HAVE_SECURE_MKSTEMP + #endif diff --git a/rsync/Makefile.in b/rsync/Makefile.in new file mode 100644 index 0000000..2cb50bc --- /dev/null +++ b/rsync/Makefile.in @@ -0,0 +1,323 @@ +# Makefile for rsync. This is processed by configure to produce the final +# Makefile + +prefix=@prefix@ +datarootdir=@datarootdir@ +exec_prefix=@exec_prefix@ +stunnel4=@STUNNEL4@ +bindir=@bindir@ +mandir=@mandir@ + +LIBS=@LIBS@ +CC=@CC@ +CFLAGS=@CFLAGS@ +CPPFLAGS=@CPPFLAGS@ +EXEEXT=@EXEEXT@ +LDFLAGS=@LDFLAGS@ + +INSTALLCMD=@INSTALL@ +INSTALLMAN=@INSTALL@ + +srcdir=@srcdir@ +MKDIR_P=@MKDIR_P@ +VPATH=$(srcdir) +SHELL=/bin/sh + +VERSION=@RSYNC_VERSION@ + +.SUFFIXES: +.SUFFIXES: .c .o + +GENFILES=configure.sh config.h.in proto.h proto.h-tstamp rsync.1 rsyncd.conf.5 +HEADERS=byteorder.h config.h errcode.h proto.h rsync.h ifuncs.h itypes.h inums.h \ + lib/pool_alloc.h +LIBOBJ=lib/wildmatch.o lib/compat.o lib/snprintf.o lib/mdfour.o lib/md5.o \ + lib/permstring.o lib/pool_alloc.o lib/sysacls.o lib/sysxattrs.o @LIBOBJS@ +zlib_OBJS=zlib/deflate.o zlib/inffast.o zlib/inflate.o zlib/inftrees.o \ + zlib/trees.o zlib/zutil.o zlib/adler32.o zlib/compress.o zlib/crc32.o +OBJS1=flist.o rsync.o generator.o receiver.o cleanup.o sender.o exclude.o \ + util.o util2.o main.o checksum.o match.o syscall.o log.o backup.o delete.o +OBJS2=options.o io.o compat.o hlink.o token.o uidlist.o socket.o hashtable.o \ + fileio.o batch.o clientname.o chmod.o acls.o xattrs.o +OBJS3=progress.o pipe.o +DAEMON_OBJ = params.o loadparm.o clientserver.o access.o connection.o authenticate.o +popt_OBJS=popt/findme.o popt/popt.o popt/poptconfig.o \ + popt/popthelp.o popt/poptparse.o +OBJS=$(OBJS1) $(OBJS2) $(OBJS3) $(DAEMON_OBJ) $(LIBOBJ) @BUILD_ZLIB@ @BUILD_POPT@ + +TLS_OBJ = tls.o syscall.o lib/compat.o lib/snprintf.o lib/permstring.o lib/sysxattrs.o @BUILD_POPT@ + +# Programs we must have to run the test cases +CHECK_PROGS = rsync$(EXEEXT) tls$(EXEEXT) getgroups$(EXEEXT) getfsdev$(EXEEXT) \ + testrun$(EXEEXT) trimslash$(EXEEXT) t_unsafe$(EXEEXT) wildtest$(EXEEXT) + +CHECK_SYMLINKS = testsuite/chown-fake.test testsuite/devices-fake.test testsuite/xattrs-hlink.test + +# Objects for CHECK_PROGS to clean +CHECK_OBJS=tls.o testrun.o getgroups.o getfsdev.o t_stub.o t_unsafe.o trimslash.o wildtest.o + +# note that the -I. is needed to handle config.h when using VPATH +.c.o: +@OBJ_SAVE@ + $(CC) -I. -I$(srcdir) $(CFLAGS) $(CPPFLAGS) -c $< @CC_SHOBJ_FLAG@ +@OBJ_RESTORE@ + +all: Makefile rsync$(EXEEXT) rsync-ssl stunnel-rsync stunnel-rsyncd.conf @MAKE_MAN@ + +install: all + -${MKDIR_P} ${DESTDIR}${bindir} + ${INSTALLCMD} ${INSTALL_STRIP} -m 755 rsync$(EXEEXT) ${DESTDIR}${bindir} + -${MKDIR_P} ${DESTDIR}${mandir}/man1 + -${MKDIR_P} ${DESTDIR}${mandir}/man5 + if test -f rsync.1; then ${INSTALLMAN} -m 644 rsync.1 ${DESTDIR}${mandir}/man1; fi + if test -f rsyncd.conf.5; then ${INSTALLMAN} -m 644 rsyncd.conf.5 ${DESTDIR}${mandir}/man5; fi + +install-ssl-client: rsync-ssl stunnel-rsync + -${MKDIR_P} ${DESTDIR}${bindir} + ${INSTALLCMD} ${INSTALL_STRIP} -m 755 rsync-ssl ${DESTDIR}${bindir} + ${INSTALLCMD} ${INSTALL_STRIP} -m 755 stunnel-rsync ${DESTDIR}${bindir} + +install-ssl-daemon: stunnel-rsyncd.conf + -${MKDIR_P} ${DESTDIR}/etc/stunnel + ${INSTALLCMD} ${INSTALL_STRIP} -m 644 stunnel-rsyncd.conf ${DESTDIR}/etc/stunnel/rsyncd.conf + @if ! ls /etc/rsync-ssl/certs/server.* >/dev/null 2>/dev/null; then \ + echo "Note that you'll need to install the certificate used by /etc/stunnel/rsyncd.conf"; \ + fi + +install-all: install install-ssl-client install-ssl-daemon + +install-strip: + $(MAKE) INSTALL_STRIP='-s' install + +rsync$(EXEEXT): $(OBJS) + $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(OBJS) $(LIBS) + +$(OBJS): $(HEADERS) +$(CHECK_OBJS): $(HEADERS) + +flist.o: rounding.h + +rounding.h: rounding.c rsync.h + @for r in 0 1 3; do \ + if $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) -o rounding -DEXTRA_ROUNDING=$$r -I. $(srcdir)/rounding.c >rounding.out 2>&1; then \ + echo "#define EXTRA_ROUNDING $$r" >rounding.h; \ + if test -f "$$HOME/build_farm/build_test.fns"; then \ + echo "EXTRA_ROUNDING is $$r" >&2; \ + fi; \ + break; \ + fi; \ + done + @rm -f rounding + @if test -f rounding.h; then : ; else \ + cat rounding.out 1>&2; \ + echo "Failed to create rounding.h!" 1>&2; \ + exit 1; \ + fi + @rm -f rounding.out + +tls$(EXEEXT): $(TLS_OBJ) + $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(TLS_OBJ) $(LIBS) + +testrun$(EXEEXT): testrun.o + $(CC) $(CFLAGS) $(LDFLAGS) -o $@ testrun.o + +getgroups$(EXEEXT): getgroups.o + $(CC) $(CFLAGS) $(LDFLAGS) -o $@ getgroups.o $(LIBS) + +getfsdev$(EXEEXT): getfsdev.o + $(CC) $(CFLAGS) $(LDFLAGS) -o $@ getfsdev.o $(LIBS) + +TRIMSLASH_OBJ = trimslash.o syscall.o lib/compat.o lib/snprintf.o +trimslash$(EXEEXT): $(TRIMSLASH_OBJ) + $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(TRIMSLASH_OBJ) $(LIBS) + +T_UNSAFE_OBJ = t_unsafe.o syscall.o util.o util2.o t_stub.o lib/compat.o lib/snprintf.o lib/wildmatch.o +t_unsafe$(EXEEXT): $(T_UNSAFE_OBJ) + $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(T_UNSAFE_OBJ) $(LIBS) + +gen: conf proto.h man + +gensend: gen + rsync -aivzc $(GENFILES) samba.org:/home/ftp/pub/rsync/generated-files/ + +conf: + cd $(srcdir) && $(MAKE) -f prepare-source.mak conf + +configure.sh config.h.in: configure.ac aclocal.m4 + @if test -f configure.sh; then cp -p configure.sh configure.sh.old; else touch configure.sh.old; fi + @if test -f config.h.in; then cp -p config.h.in config.h.in.old; else touch config.h.in.old; fi + autoconf -o configure.sh + autoheader && touch config.h.in + @if diff configure.sh configure.sh.old >/dev/null 2>&1; then \ + echo "configure.sh is unchanged."; \ + rm configure.sh.old; \ + else \ + echo "configure.sh has CHANGED."; \ + fi + @if diff config.h.in config.h.in.old >/dev/null 2>&1; then \ + echo "config.h.in is unchanged."; \ + rm config.h.in.old; \ + else \ + echo "config.h.in has CHANGED."; \ + fi + @if test -f configure.sh.old -o -f config.h.in.old; then \ + if test "$(MAKECMDGOALS)" = reconfigure; then \ + echo 'Continuing with "make reconfigure".'; \ + else \ + echo 'You may need to run:'; \ + echo ' make reconfigure'; \ + exit 1; \ + fi \ + fi + +reconfigure: configure.sh + ./config.status --recheck + ./config.status + +Makefile: Makefile.in config.status configure.sh config.h.in + @if test -f Makefile; then cp -p Makefile Makefile.old; else touch Makefile.old; fi + @./config.status + @if diff Makefile Makefile.old >/dev/null 2>&1; then \ + echo "Makefile is unchanged."; \ + rm Makefile.old; \ + else \ + if test "$(MAKECMDGOALS)" = reconfigure; then \ + echo 'Continuing with "make reconfigure".'; \ + else \ + echo "Makefile updated -- rerun your make command."; \ + exit 1; \ + fi \ + fi + +rsync-ssl: $(srcdir)/rsync-ssl.in Makefile + sed 's;\@bindir\@;$(bindir);g' <$(srcdir)/rsync-ssl.in >rsync-ssl + @chmod +x rsync-ssl + +stunnel-rsync: $(srcdir)/stunnel-rsync.in Makefile + sed 's;\@stunnel4\@;$(stunnel4);g' <$(srcdir)/stunnel-rsync.in >stunnel-rsync + @chmod +x stunnel-rsync + +stunnel-rsyncd.conf: $(srcdir)/stunnel-rsyncd.conf.in Makefile + sed 's;\@bindir\@;$(bindir);g' <$(srcdir)/stunnel-rsyncd.conf.in >stunnel-rsyncd.conf + +proto: proto.h-tstamp + +proto.h: proto.h-tstamp + @if test -f proto.h; then :; else cp -p $(srcdir)/proto.h .; fi + +proto.h-tstamp: $(srcdir)/*.c $(srcdir)/lib/compat.c config.h + perl $(srcdir)/mkproto.pl $(srcdir)/*.c $(srcdir)/lib/compat.c + +man: rsync.1 rsyncd.conf.5 man-copy + +man-copy: + @-if test -f rsync.1; then :; else echo 'Copying srcdir rsync.1'; cp -p $(srcdir)/rsync.1 .; fi + @-if test -f rsyncd.conf.5; then :; else echo 'Copying srcdir rsyncd.conf.5'; cp -p $(srcdir)/rsyncd.conf.5 .; fi + +rsync.1: rsync.yo + yodl2man -o rsync.1 $(srcdir)/rsync.yo + -$(srcdir)/tweak_manpage rsync.1 + +rsyncd.conf.5: rsyncd.conf.yo + yodl2man -o rsyncd.conf.5 $(srcdir)/rsyncd.conf.yo + -$(srcdir)/tweak_manpage rsyncd.conf.5 + +clean: cleantests + rm -f *~ $(OBJS) $(CHECK_PROGS) $(CHECK_OBJS) $(CHECK_SYMLINKS) \ + rounding rounding.h *.old + +cleantests: + rm -rf ./testtmp* + +# We try to delete built files from both the source and build +# directories, just in case somebody previously configured things in +# the source directory. +distclean: clean + rm -f Makefile config.h config.status + rm -f rsync-ssl stunnel-rsync stunnel-rsyncd.conf + rm -f lib/dummy popt/dummy zlib/dummy + rm -f $(srcdir)/Makefile $(srcdir)/config.h $(srcdir)/config.status + rm -f $(srcdir)/lib/dummy $(srcdir)/popt/dummy $(srcdir)/zlib/dummy + rm -f config.cache config.log + rm -f $(srcdir)/config.cache $(srcdir)/config.log + rm -f shconfig $(srcdir)/shconfig + rm -f $(GENFILES) + rm -rf autom4te.cache + +# this target is really just for my use. It only works on a limited +# range of machines and is used to produce a list of potentially +# dead (ie. unused) functions in the code. (tridge) +finddead: + nm *.o */*.o |grep 'U ' | awk '{print $$2}' | sort -u > nmused.txt + nm *.o */*.o |grep 'T ' | awk '{print $$3}' | sort -u > nmfns.txt + comm -13 nmused.txt nmfns.txt + +# 'check' is the GNU name, 'test' is the name for everybody else :-) +.PHONY: check test + +test: check + +# There seems to be no standard way to specify some variables as +# exported from a Makefile apart from listing them like this. + +# This depends on building rsync; if we need any helper programs it +# should depend on them too. + +# We try to run the scripts with POSIX mode on, in the hope that will +# catch Bash-isms earlier even if we're running on GNU. Of course, we +# might lose in the future where POSIX diverges from old sh. + +check: all $(CHECK_PROGS) $(CHECK_SYMLINKS) + rsync_bin=`pwd`/rsync$(EXEEXT) $(srcdir)/runtests.sh + +check29: all $(CHECK_PROGS) $(CHECK_SYMLINKS) + rsync_bin=`pwd`/rsync$(EXEEXT) $(srcdir)/runtests.sh --protocol=29 + +check30: all $(CHECK_PROGS) $(CHECK_SYMLINKS) + rsync_bin=`pwd`/rsync$(EXEEXT) $(srcdir)/runtests.sh --protocol=30 + +wildtest.o: wildtest.c lib/wildmatch.c rsync.h config.h +wildtest$(EXEEXT): wildtest.o lib/compat.o lib/snprintf.o @BUILD_POPT@ + $(CC) $(CFLAGS) $(LDFLAGS) -o $@ wildtest.o lib/compat.o lib/snprintf.o @BUILD_POPT@ $(LIBS) + +testsuite/chown-fake.test: + ln -s chown.test $(srcdir)/testsuite/chown-fake.test + +testsuite/devices-fake.test: + ln -s devices.test $(srcdir)/testsuite/devices-fake.test + +testsuite/xattrs-hlink.test: + ln -s xattrs.test $(srcdir)/testsuite/xattrs-hlink.test + +# This does *not* depend on building or installing: you can use it to +# check a version installed from a binary or some other source tree, +# if you want. + +installcheck: $(CHECK_PROGS) $(CHECK_SYMLINKS) + POSIXLY_CORRECT=1 TOOLDIR=`pwd` rsync_bin="$(bindir)/rsync$(EXEEXT)" srcdir="$(srcdir)" $(srcdir)/runtests.sh + +# TODO: Add 'dist' target; need to know which files will be included + +# Run the SPLINT (Secure Programming Lint) tool. +.PHONY: splint +splint: + splint +unixlib +gnuextensions -weak rsync.c + + +rsync.dvi: doc/rsync.texinfo + texi2dvi -o $@ $< + +rsync.ps: rsync.dvi + dvips -ta4 -o $@ $< + +rsync.pdf: doc/rsync.texinfo + texi2dvi -o $@ --pdf $< + + +doxygen: + cd $(srcdir) && rm dox/html/* && doxygen + +# for maintainers only +doxygen-upload: + rsync -avzv $(srcdir)/dox/html/ --delete \ + samba.org:/home/httpd/html/rsync/doxygen/head/ diff --git a/rsync/NEWS b/rsync/NEWS new file mode 100644 index 0000000..1cbbe9b --- /dev/null +++ b/rsync/NEWS @@ -0,0 +1,115 @@ +NEWS for rsync 3.1.1 (22 Jun 2014) +Protocol: 31 (unchanged) +Changes since 3.1.0: + + BUG FIXES: + + - If the receiver gets bogus filenames from the sender (an unexpected + leading slash or a ".." infix dir), exit with an error. This prevents a + malicious sender from trying to inject filenames that would affect an + area outside the destination directories. + + - Fixed a failure to remove the partial-transfer temp file when interrupted + (and rsync is not saving the partial files). + + - Changed the chown/group/xattr-set order to avoid losing some security- + related xattr info (that would get cleared by a chown). + + - Fixed a bug in the xattr-finding code that could make a non-root-run + receiver not able to find some xattr numbers. + + - Fixed a bug in the early daemon protocol where a timeout failed to be + honored (e.g. if the remote side fails to send us the initial protocol + greeting). + + - Fixed unintended inclusion of commas in file numbers in the daemon log. + + - We once again send the 'f' sub-flag (of -e) to the server side so it + knows that we can handle incremental-recursion directory errors properly + in older protocols. + + - Fixed an issue with too-aggressive keep-alive messages causing a problem + for older rsync versions early in the transfer. + + - Fixed an incorrect message about backup-directory-creation when using + --dry-run and the backup dir is not an absolute path. + + - Fixed a bug where a failed deletion and/or a failed sender-side removal + would not affect the exit code. + + - Fixed a bug that caused a failure when combining --delete-missing-args + with --xattrs and/or --acls. + + - Fixed a strange dir_depth assertion error that was caused by empty-dir + removals and/or duplicate files in the transfer. + + - Fixed a problem with --info=progress2's output stats where rsync would + only update the stats at the end of each file's transfer. It now uses + the data that is flowing for the current file, making the stats more + accurate and less jumpy. + + - Fixed an itemize bug that affected the combo of --link-dest, -X, and -n. + + - Fixed a problem with delete messages not appearing in the log file when + the user didn't use --verbose. + + - Improve chunked xattr reading for OS X. + + - Removed an attempted hard-link xattr optimization that was causing a + transfer failure. This removal is flagged in the compatibility code, so + if a better fix can be discovered, we have a way to flip it on again. + + - Fixed a bug when the receiver is not configured to be able to hard link + symlimks/devices/special-file items but the sender sent some of these + items flagged as hard-linked. + + - We now generate a better error if the buffer overflows in do_mknod(). + + - Fixed a problem reading more than 16 ACLs on some OSes. + + - Fixed the reading of the secrets file to avoid an infinite wait when + the username is missing. + + - Fixed a parsing problem in the --usermap/--groupmap options when using + MIN-MAX numbers. + + - Switched Cygwin back to using socketpair "pipes" to try to speed it up. + + - Added knowledge of a few new options to rrsync. + + ENHANCEMENTS: + + - Tweaked the temp-file naming when --temp-dir=DIR is used: the temp-file + names will not get a '.' prepended. + + - Added support for a new-compression idiom that does not compress all the + matching data in a transfer. This can help rsync to use less cpu when a + transfer has a lot of matching data, and also makes rsync compatible with + a non-bundled zlib. See the --new-compress and --old-compress options in + the manpage. + + - Added the support/rsync-no-vanished wrapper script. + + - Made configure more prominently mention when we failed to find yodl (in + case the user wants to be able to generate manpages from *.yo files). + + - Have manpage mention how a daemon's max-verbosity setting affects info + and debug options. Also added more clarification on backslash removals + for excludes that contain wildcards. + + - Have configure check if for the attr lib (for getxattr) for those systems + that need to link against it explicitly. + + - Change the early dir-creation logic to only use that idiom in an + inc-recursive copy that is preserving directory times. e.g. using + --omit-dir-times will avoid these early directories being created. + + - Fix a bug in cmp_time() that would return a wrong result if the 2 times + differed by an amount greater than what a time_t can hold. + + DEVELOPER RELATED: + + - We now include an example systemd file (in packaging/systemd). + + - Tweaked configure to make sure that any intended use of the included popt + and/or zlib code is put early in the CFLAGS. diff --git a/rsync/OLDNEWS b/rsync/OLDNEWS new file mode 100644 index 0000000..ba02b07 --- /dev/null +++ b/rsync/OLDNEWS @@ -0,0 +1,3599 @@ +NEWS for rsync 3.1.0 (28 Sep 2013) +Protocol: 31 (changed) +Changes since 3.0.9: + + OUTPUT CHANGES: + + - Output numbers in 3-digit groups by default (e.g. 1,234,567). See the + --human-readable option for a way to turn it off. See also the daemon's + "log format" parameter and related command-line options (including + --out-format) for a modifier that can be used to request digit-grouping + or human-readable output in log escapes. (Note that log output is + unchanged by default.) + + - The --list-only option is now affected by the --human-readable setting. + It will display digit groupings by default, and unit suffixes if higher + levels of readability are requested. Also, the column width for the size + output has increased from 11 to 14 characters when human readability is + enabled. Use --no-h to get the old-style output and column size. + + - The output of the --progress option has changed: the string "xfer" was + shortened to "xfr", and the string "to-check" was shortened to "to-chk", + both designed to make room for the (by default) wider display of file + size numbers without making the total line-length longer. Also, when + incremental recursion is enabled, the string "ir-chk" will be used + instead of "to-chk" up until the incremental-recursion scan is done, + letting you know that the value to check and the total value will still + be increasing as new files are found. + + - Enhanced the --stats output: 1) to mention how many files were created + (protocol >= 28), 2) to mention how many files were deleted (a new line + for protocol 31, but only output when --delete is in effect), and 3) to + follow the file-count, created-count, and deleted-count with a subcount + list that shows the counts by type. The wording of the transferred count + has also changed so that it is clearer that it is only a count of regular + files. + + BUG FIXES: + + - Fixed a bug in the iconv code when EINVAL or EILSEQ is returned with a + full output buffer. + + - Fixed some rare bugs in --iconv processing that might cause a multibyte + character to get translated incorrectly. + + - Fixed a bogus "vanished file" error if some files were specified with + "./" prefixes and others were not. + + - Fixed a bug in --sparse where an extra gap could get inserted after a + partial write. + + - Changed the way --progress overwrites its prior output in order to make + it nearly impossible for the progress to get overwritten by an error. + + - Improved the propagation of abnormal-exit error messages. This should + help the client side to receive errors from the server when it is exiting + abnormally, and should also avoid dying with an "connection unexpectedly + closed" exit when the closed connection is really expected. + + - The sender now checks each file it plans to remove to ensure that it + hasn't changed from the first stat's info. This helps to avoid losing + file data when the user is not using the option in a safe manner. + + - Fixed a data-duplication bug in the compress option that made compression + less efficient. This improves protocol 31 onward, while behaving in a + compatible (buggy) manner with older rsync protocols. + + - When creating a temp-file, rsync is now a bit smarter about it dot-char + choices, which can fix a problem on OS X with names that start with "..". + + - Rsync now sets a cleanup flag for --inplace and --append transfers that + will flush the write buffer if the transfer aborts. This ensures that + more received data gets written out to the disk on an aborted transfer + (which is quite helpful on a slow, flaky connection). + + - The reads that map_ptr() now does are aligned on 1K boundaries. This + helps some filesystems and/or files that don't like unaligned reads. + + - Fix an issue in the msleep() function if time jumps backwards. + + - Fix daemon-server module-name splitting bug where an arg would get split + even if --protect-args was used. + + ENHANCEMENTS: + + - Added the --remote-option=OPT (-M OPT) command-line option that is useful + for things like sending a remote --log-file=FILE or --fake-super option. + + - Added the --info=FLAGS and --debug=FLAGS options to allow finer-grained + control over what is output. Added an extra type of --progress output + using --info=progress2. + + - The --msgs2stderr option can help with debugging rsync by allowing the + debug messages to get output to stderr rather than travel via the socket + protocol. + + - Added the --delete-missing-args and --ignore-missing-args options to + either delete or ignore user-specified files on the receiver that are + missing on the sender (normally the absence of user-specified files + generates an error). + + - Added a "T" (terabyte) category to the --human-readable size suffixes. + + - Added the --usermap/--groupmap/--chown options for manipulating file + ownership during the copy. + + - Added the "%C" escape to the log-output handling, which will output the + MD5 checksum of any transferred file, or all files if --checksum was + specified (when protocol 30 or above is in effect). + + - Added the "reverse lookup" parameter to the rsync daemon config file to + allow reverse-DNS lookups to be disabled. + + - Added a forward-DNS lookup for the daemon's hosts allow/deny config. Can + be disabled via "forward lookup" parameter (defaults to enabled). + + - Added a way for more than one group to be specified in the daemon's + config file, including a way to specify that you want all of the + specified user's groups without having to name them. Also changed the + daemon to complain about an inability to set explicitly-specified uid/gid + values, even when not run by a super-user. + + - The daemon now tries to send the user the error messages from the + pre-xfer exec script when it fails. + + - Improved the use of alt-dest options into an existing hierarchy of files: + If a match is found in an alt-dir, it takes precedence over an existing + file. (We'll need to wait for a future version before attribute-changes + on otherwise unchanged files are safe when using an existing hierarchy.) + + - Added per-user authorization options and group-authorization support to + the daemon's "auth users" parameter. + + - Added a way to reference environment variables in a daemon's config file + (using %VAR% references). + + - When replacing a non-dir with a symlink/hard-link/device/special-file, + the update should now be done in an atomic manner. + + - Avoid re-sending xattr info for hard-linked files w/the same xattrs + (protocol 31). + + - The backup code was improved to use better logic maintaining the backup + directory hierarchy. Also, when a file is being backed up, rsync tries + to hard-link it into place so that the upcoming replacement of the + destination file will be atomic (for the normal, non-inplace logic). + + - Added the ability to synchronize nano-second modified times. + + - Added a few more default suffixes for the "dont compress" settings. + + - Added the checking of the RSYNC_PROTECT_ARGS environment variable to allow + the default for the --protect-args command-line option to be overridden. + + - Added the --preallocate command-line option. + + - Allow --password-file=- to read the password from stdin (filename "-"). + + - Rsync now comes packaged with an rsync-ssl helper script that can be + used to contact a remote rsync daemon using a piped-stunnel command. + It also includes an stunnel config file to run the server side to + support ssl daemon connections. See the packaging/lsb/rsync.spec + file for one way to package the resulting files. (Suggestions for + how to make this even easier to install & use are welcomed.) + + - Improved the speed of some --inplace updates when there are lots of + identical checksum blocks that end up being unusable. + + - Added the --outbuf=N|L|B option for choosing the output buffering. + + - Repeating the --fuzzy option now causes the code to look for fuzzy + matches inside alt-dest directories too. + + - The --chmod option now supports numeric modes, e.g. --chmod=644,D755 + + - Added some Solaris xattr code. + + - Made an rsync daemon (the listening process) exit with a 0 status when + it was signaled to die. This helps launchd. + + - Improved the RSYNC_* environment variables for the pre-xfer exec script: + when a daemon is sent multiple request args, they are now joined into a + single return value (separated by spaces) so that the RSYNC_REQUEST + environment variable is accurate for any "pre-xfer exec". The values in + RSYNC_ARG# vars are no longer truncated at the "." arg (prior to the + request dirs/files), so that all the requested values are also listed + (separately) in RSYNC_ARG# variables. + + EXTRAS: + + - Added an "instant-rsyncd" script to the support directory, which makes + it easy to configure a simple rsync daemon in the current directory. + + - Added the "mapfrom" and "mapto" scripts to the support directory, which + makes it easier to do user/group mapping in a local transfer based on + passwd/group files from another machine. + + - There's a new, improved version of the lsh script in the support dir: + it's written in perl and supports -u without resorting to using sudo + (when run as root). The old shell version is now named lsh.sh. + + - There is a helper script named rsync-slash-strip in the support directory + for anyone that wants to change the way rsync handles args with trailing + slashes. (e.g. arg/ would get stripped to arg while arg/. would turn into + arg/). + + INTERNAL: + + - The I/O code was rewritten to be simpler and do bigger buffered reads + over the socket. The I/O between the receiver and the generator was + changed to be standard multiplexed-I/O (like that over the socket). + + - The sender tries to use any dead time while the generator is looking for + files to transfer in order to do sender-side directory scanning in a more + parallel manner. + + - A daemon can now inform a client about a daemon-configured timeout value + so that the client can assist in the keep-alive activity (protocol 31). + + - The filter code received some refactoring to make it more extendible, to + read better, and do better sanity checking. + + - Really big numbers are now output using our own big-num routine rather + than casting them to a double and using a %.0f conversion. + + - The pool_alloc library has received some minor improvements in alignment + handling. + + - Added init_stat_x() function to avoid duplication of acl/xattr init code. + + - The included zlib was upgraded from 1.2.3 to 1.2.8. + + - Rsync can now be compiled to use an unmodified zlib library instead of + the tweaked one that is included with rsync. This will eventually + become the default, at which point we'll start the countdown to removing + the included zlib. Until then, feel free to configure using: + + ./configure --with-included-zlib=no + + DEVELOPER RELATED: + + - Added more conditional debug output. + + - Fixed some build issues for android and minix. + +NEWS for rsync 3.0.9 (23 Sep 2011) +Protocol: 30 (unchanged) +Changes since 3.0.8: + + BUG FIXES: + + - Fix a crash bug in checksum scanning when --inplace is used. + + - Fix a hang if a hard-linked file cannot be opened by the sender (e.g. + if it has no read permission). + + - Fix preservation of a symlink's system xattrs (e.g. selinux) on Linux. + + - Fix a memory leak in the xattr code. + + - Fixed a bug with --delete-excluded when a filter merge file has a rule + that specifies a receiver-only side restriction. + + - Fix a bug with the modifying of unwritable directories. + + - Fix --fake-super's interaction with --link-dest same-file comparisons. + + - Fix the updating of the curr_dir buffer to avoid a duplicate slash. + + - Fix the directory permissions on an implied dot-dir when using --relative + (e.g. /outside/path/././send/path). + + - Fixed some too-long sleeping instances when using --bwlimit. + + - Fixed when symlink ownership difference-checking gets compiled into + unchanged_attrs(). + + - Improved the socket-error reporting when multiple protocols fail. + + - Fixed a case where a socket error could reference just-freed memory. + + - Failing to use a password file that was specified on the command-line is + now a fatal error. + + - Fix the non-root updating of directories that don't have the read and/or + execute permission. + + - Make daemon-excluded file errors more error-like. + + - Fix a compilation issue on older C compilers (due to a misplaced var + declaration). + + - Make configure avoid finding socketpair on cygwin. + + - Avoid trying to reference SO_BROADCAST if the OS doesn't support it. + + - Fix some issues with the post-processing of the man pages. + + - Fixed the user home-dir handling in the support/lsh script. + + - Some minor manpage improvements. + +NEWS for rsync 3.0.8 (26 Mar 2011) +Protocol: 30 (unchanged) +Changes since 3.0.7: + + BUG FIXES: + + - Fixed two buffer-overflow issues: one where a directory path that is + exactly MAXPATHLEN was not handled correctly, and one handling a + --backup-dir that is extra extra large. + + - Fixed a data-corruption issue when preserving hard-links without + preserving file ownership, and doing deletions either before or during + the transfer (CVE-2011-1097). This fixes some assert errors in the + hard-linking code, and some potential failed checksums (via -c) that + should have matched. + + - Fixed a potential crash when an rsync daemon has a filter/exclude list + and the transfer is using ACLs or xattrs. + + - Fixed a hang if a really large file is being processed by an rsync that + can't handle 64-bit numbers. Rsync will now complain about the file + being too big and skip it. + + - For devices and special files, we now avoid gathering useless ACL and/or + xattr information for files that aren't being copied. (The un-copied + files are still put into the file list, but there's no need to gather + data that is not going to be used.) This ensures that if the user uses + --no-D, that rsync can't possibly complain about being unable to gather + extended information from special files that are in the file list (but + not in the transfer). + + - Properly handle requesting remote filenames that start with a dash. This + avoids a potential error where a filename could be interpreted as a + (usually invalid) option. + + - Fixed a bug in the comparing of upper-case letters in file suffixes for + --skip-compress. + + - If an rsync daemon has a module configured without a path setting, rsync + will now disallow access to that module. + + - If the destination arg is an empty string, it will be treated as a + reference to the current directory (as 2.x used to do). + + - If rsync was compiled with a newer time-setting function (such as + lutimes), rsync will fall-back to an older function (such as utimes) on a + system where the newer function is not around. This helps to make the + rsync binary more portable in mixed-OS-release situations. + + - Fixed a batch-file writing bug that would not write out the full set of + compatibility flags that the transfer was using. This fixes a potential + protocol problem for a batch file that contains a sender-side I/O error: + it would have been sent in a way that the batch-reader wasn't expecting. + + - Some improvements to the hard-linking code to ensure that device-number + hashing is working right, and to supply more information if the hard-link + code fails. + + - The --inplace code was improved to not search for an impossible checksum + position. The quadruple-verbose chunk[N] message will now mention when + an inplace chunk was handled by a seek rather than a read+write. + + - Improved ACL mask handling, e.g. for Solaris. + + - Fixed a bug that prevented --numeric-ids from disabling the translation + of user/group IDs for ACLs. + + - Fixed an issue where an xattr and/or ACL transfer that used an alt-dest + option (e.g. --link-dest) could output an error trying to itemize the + changes against the alt-dest directory's xattr/ACL info but was instead + trying to access the not-yet-existing new destination directory. + + - Improved xattr system-error messages to mention the full path to the + file. + + - The --link-dest checking for identical symlinks now avoids considering + attribute differences that cannot be changed on the receiver. + + - Avoid trying to read/write xattrs on certain file types for certain OSes. + Improved configure to set NO_SYMLINK_XATTRS, NO_DEVICE_XATTRS, and/or + NO_SPECIAL_XATTRS defines in config.h. + + - Improved the unsafe-symlink errors messages. + + - Fixed a bug setting xattrs on new files that aren't user writable. + + - Avoid re-setting xattrs on a hard-linked file w/the same xattrs. + + - Fixed a bug with --fake-super when copying files and dirs that aren't + user writable. + + - Fixed a bug where a sparse file could have its last sparse block turned + into a real block when rsync sets the file size (requires ftruncate). + + - If a temp-file name is too long, rsync now avoids truncating the name in + the middle of adjacent high-bit characters. This prevents a potential + filename error if the filesystem doesn't allow a name to contain an + invalid multi-byte sequence. + + - If a muli-protocol socket connection fails (i.e., when contacting a + daemon), we now report all the failures, not just the last one. This + avoids losing a relevant error (e.g. an IPv4 connection-refused error) + that happened before the final error (e.g. an IPv6 protocol-not-supported + error). + + - Generate a transfer error if we try to call chown with a -1 for a uid or + a gid (which is not settable). + + - Fixed the working of --force when used with --one-file-system. + + - Fix the popt arg parsing so that an option that doesn't take an arg will + reject an attempt to supply one (can configure --with-included-popt if + your system's popt library doesn't yet have this fix). + + - A couple minor option tweaks to the support/rrsync script, and also some + regex changes that make vim highlighting happier. + + - Fixed some issues in the support/mnt-excl script. + + - Various manpage improvements. + + ENHANCEMENTS: + + - Added ".hg/" to the default cvs excludes (see -C & --cvs-exclude). + + DEVELOPER RELATED: + + - Use lchmod() whenever it is available (not just on symlinks). + + - A couple fixes to the socketpair_tcp() routine. + + - Updated the helper scripts in the packaging subdirectory. + + - Renamed configure.in to configure.ac. + + - Fixed configure's checking for iconv routines for newer OS X versions. + + - Fixed the testsuite/xattrs.test script on OS X. + +NEWS for rsync 3.0.7 (31 Dec 2009) +Protocol: 30 (unchanged) +Changes since 3.0.6: + + BUG FIXES: + + - Fixed a bogus free when using --xattrs with --backup. + + - Avoid an error when --dry-run was trying to stat a prior hard-link file + that hasn't really been created. + + - Fixed a problem with --compress (-z) where the receiving side could + return the error "inflate (token) returned -5". + + - Fixed a bug where --delete-during could delete in a directory before it + noticed that the sending side sent an I/O error for that directory (both + sides of the transfer must be at least 3.0.7). + + - Improved --skip-compress's error handling of bad character-sets and got + rid of a lingering debug fprintf(). + + - Fixed the daemon's conveyance of io_error value from the sender. + + - An rsync daemon use seteuid() (when available) if it used setuid(). + + - Get the permissions right on a --fake-super transferred directory that + needs more owner permissions to emulate root behavior. + + - An absolute-path filter rule (i.e. with a '/' modifier) no longer loses + its modifier when sending the filter rules to the remote rsync. + + - Improved the "--delete does not work without -r or -d" message. + + - Improved rsync's handling of --timeout to avoid a weird timeout case + where the sender could timeout even though it has recently written data + to the socket (but hasn't read data recently, due to the writing). + + - Some misc manpage improvements. + + - Fixed the chmod-temp-dir testsuite on a system without /var/tmp. + + - Make sure that a timeout specified in the daemon's config is used as a + maximum timeout value when the user also specifies a timeout. + + - Improved the error-exit reporting when rsync gets an error trying to + cleanup after an error: the initial error is reported. + + - Improved configure's detection of IPv6 for solaris and cygwin. + + - The AIX sysacls routines will now return ENOSYS if ENOTSUP is missing. + + - Made our (only used if missing) getaddrinfo() routine use inet_pton() + (which we also provide) instead of inet_aton(). + + - The exit-related debug messages now mention the program's role so it is + clear who output what message. + + DEVELOPER RELATED: + + - Got rid of type-punned compiler warnings output by newer gcc versions. + + - The Makefile now ensures that proto.h will be rebuilt if config.h changes. + + - The testsuite no longer uses "id -u", so it works better on solaris. + + +NEWS for rsync 3.0.6 (8 May 2009) +Protocol: 30 (unchanged) +Changes since 3.0.5: + + BUG FIXES: + + - Fixed a --read-batch hang when rsync is reading a batch file that was + created from an incremental-recursion transfer. + + - Fixed the daemon's socket code to handle the simultaneous arrival of + multiple connections. + + - Fix --safe-links/--copy-unsafe-links to properly handle symlinks that + have consecutive slashes in the value. + + - Fixed the parsing of an [IPv6_LITERAL_ADDR] when a USER@ is prefixed. + + - The sender now skips a (bogus) symlink that has a 0-length value, which + avoids a transfer error in the receiver. + + - Fixed a case where the sender could die with a tag-0 error if there was + an I/O during the sending of the file list. + + - Fixed the rrsync script to avoid a server-side problem when -e is at the + start of the short options. + + - Fixed a problem where a vanished directory could turn into an exit code + 23 instead of the proper exit code 24. + + - Fixed the --iconv conversion of symlinks when doing a local copy. + + - Fixed a problem where --one-file-system was not stopping deletions on the + receiving side when a mount-point directory did not match a directory in + the transfer. + + - Fixed the dropping of an ACL mask when no named ACL values were present. + + - Fixed an ACL/xattr corruption issue where the --backup option could cause + rsync to associate the wrong ACL/xattr information with received files. + + - Fixed the use of --xattrs with --only-write-batch. + + - Fixed the use of --dry-run with --read-batch. + + - Fixed configure's erroneous use of target. + + - Fixed configure's --disable-debug option. + + - Fixed a run-time issue for systems that can't find iconv_open() by adding + the --disable-iconv-open configure option. + + - Complain and die if the user tries to combine --remove-source-files (or + the deprecated --remove-sent-files) with --read-batch. + + - Fixed an failure transferring special files from Solaris to Linux. + + +NEWS for rsync 3.0.5 (28 Dec 2008) +Protocol: 30 (unchanged) +Changes since 3.0.4: + + BUG FIXES: + + - Initialize xattr data in a couple spots in the hlink code, which avoids a + crash when the xattr pointer's memory happens to start out non-zero. + Also fixed the itemizing of an alt-dest file's xattrs when hard-linking. + + - Don't send a bogus "-" option to an older server if there were no short + options specified. + + - Fixed skipping of unneeded updates in a batch file when incremental + recursion is active. Added a test for this. Made batch-mode handle + "redo" files properly (and without hanging). + + - Fix the %P logfile escape when the daemon logs from inside a chroot. + + - Fixed the use of -s (--protect-args) when used with a remote source or + destination that had an empty path (e.g. "host:"). Also fixed a problem + when -s was used when accessing a daemon via a remote-shell. + + - Fixed the use of a dot-dir path (e.g. foo/./bar) inside a --files-from + file when the root of the transfer isn't the current directory. + + - Fixed a bug with "-K --delete" removing symlinks to directories when + incremental recursion is active. + + - Fixed a hard to trigger hang when using --remove-source-files. + + - Got rid of an annoying delay when accessing a daemon via a remote-shell. + + - Properly ignore (superfluous) source args on a --read-batch command. + + - Improved the manpage's description of the '*' wildcard to remove the + confusing "non-empty" qualifier. + + - Fixed reverse lookups in the compatibility-library version of + getnameinfo(). + + - Fixed a bug when using --sparse on a sparse file that has over 2GB of + consecutive sparse data. + + - Avoid a hang when using at least 3 --verbose options on a transfer with a + client sender (which includes local copying). + + - Fixed a problem with --delete-delay reporting an error when it was ready + to remove a directory that was now gone. + + - Got rid of a bunch of "warn_unused_result" compiler warnings. + + - If an ftruncate() on a received file fails, it now causes a partial- + transfer warning. + + - Allow a path with a leading "//" to be preserved (CYGWIN only). + + ENHANCEMENTS: + + - Made the support/atomic-rsync script able to perform a fully atomic + update of the copied hierarchy when the destination is setup using a + particular symlink idiom. + + +NEWS for rsync 3.0.4 (6 Sep 2008) +Protocol: 30 (unchanged) +Changes since 3.0.3: + + BUG FIXES: + + - Fixed a bug in the hard-linking code where it would sometimes try to + allocate 0 bytes of memory (which fails on some OSes, such as AIX). + + - Fixed the hard-linking of files from a device that has a device number + of 0 (which seems to be a common device number on NetBSD). + + - Fixed the handling of a --partial-dir that cannot be created. This + particularly impacts the --delay-updates option (since the files cannot + be delayed without a partial-dir), and was potentially destructive if + the --remove-source-files was also specified. + + - Fixed a couple issues in the --fake-super handling of xattrs when the + destination files have root-level attributes (e.g. selinux values) that + a non-root copy can't affect. + + - Improved the keep-alive check in the generator to fire consistently in + incremental-recursion mode when --timeout is enabled. + + - The --iconv option now converts the content of a symlink too, instead + of leaving it in the wrong character-set (requires 3.0.4 on both sides + of the transfer). + + - When using --iconv, if a filename fails to convert on the receiving side, + this no longer makes deletions in the root-dir of the transfer fail + silently (the user now gets a warning about deletions being disabled + due to IO error as long as --ignore-errors was not specified). + + - When using --iconv, if a server-side receiver can't convert a filename, + the error message sent back to the client no longer mangles the name + with the wrong charset conversion. + + - Fixed a potential alignment issue in the IRIX ACL code when allocating + the initial "struct acl" object. Also, cast mallocs to avoid warnings. + + - Changed some errors that were going to stdout to go to stderr. + + - Made human_num() and human_dnum() able to output a negative number + (rather than outputting a cryptic string of punctuation). + + ENHANCEMENTS: + + - Rsync will avoid sending an -e option to the server if an older protocol + is requested (and thus the option would not be useful). This lets the + user specify the --protocol=29 option to access an overly-restrictive + server that is rejecting the protocol-30 use of -e to the server. + + - Improved the message output for an RERR_PARTIAL exit. + + DEVELOPER RELATED: + + - The Makefile will not halt for just a timestamp change on the Makefile + or the configure files, only for actual changes in content. + + - Changed some commands in the testsuite's xattrs.test that called "rsync" + instead of "$RSYNC". + + - Enhanced the release scripts to be able to handle a branch release and + to do even more consistency checks on the files. + + +NEWS for rsync 3.0.3 (29 Jun 2008) +Protocol: 30 (unchanged) +Changes since 3.0.2: + + BUG FIXES: + + - Fixed a wildcard matching problem in the daemon when a module has + "use chroot" enabled. + + - Fixed a crash bug in the hard-link code. + + - Fixed the sending of xattr directory information when the code finds a + --link-dest or --copy-dest directory with unchanged xattrs -- the + destination directory now gets these unchanged xattrs properly applied. + + - Fixed an xattr-sending glitch that could cause an "Internal abbrev" + error. + + - Fixed the combination of --xattrs and --backup. + + - The generator no longer allows a '.' dir to be excluded by a daemon- + exclude rule. + + - Fixed deletion handling when copying a single, empty directory (with no + files) to a differently named, non-existent directory. + + - Fixed the conversion of spaces into dashes in the %M log escape. + + - Fixed several places in the code that were not returning the right + errno when a function failed. + + - Fixed the backing up of a device or special file into a backup dir. + + - Moved the setting of the socket options prior to the connect(). + + - If rsync exits in the middle of a --progress output, it now outputs a + newline to help prevent the progress line from being overwritten. + + - Fixed a problem with how a destination path with a trailing slash or + a trailing dot-dir was compared against the daemon excludes. + + - Fixed the sending of large (size > 16GB) files when talking to an older + rsync (protocols < 30): we now use a compatible block size limit. + + - If a file's length is so huge that we overflow a checksum buffer count + (i.e. several hundred TB), warn the user and avoid sending an invalid + checksum struct over the wire. + + - If a source arg is excluded, --relative no longer adds the excluded + arg's implied dirs to the transfer. This fix also made the exclude + check happen in the better place in the sending code. + + - Use the overflow_exit() function for overflows, not out_of_memory(). + + - Improved the code to better handle a system that has only 32-bit file + offsets. + + ENHANCEMENTS: + + - The rsyncd.conf manpage now consistently refers to the parameters in + the daemon config file as "parameters". + + - The description of the --inplace option was improved. + + EXTRAS: + + - Added a new script in the support directory, deny-rsync, which allows + an admin to (temporarily) replace the rsync command with a script that + sends an error message to the remote client via the rsync protocol. + + DEVELOPER RELATED: + + - Fixed a testcase failure if the tests are run as root and made some + compatibility improvements. + + - Improved the daemon tests, including checking module comments, the + listing of files, and the ensuring that daemon excludes can't affect + a dot-dir arg. + + - Improved some build rules for those that build in a separate directory + from the source, including better install rules for the man pages, and + the fixing of a proto.h-tstamp rule that could make the binaries get + rebuild without cause. + + - Improved the testsuite to work around a problem with some utilities + (e.g. cp -p & touch -r) rounding sub-second timestamps. + + - Ensure that the early patches don't cause any generated-file hunks to + bleed-over into patches that follow. + + +NEWS for rsync 3.0.2 (8 Apr 2008) +Protocol: 30 (unchanged) +Changes since 3.0.1: + + BUG FIXES: + + - Fixed a potential buffer overflow in the xattr code. + + ENHANCEMENTS: + + - None. + + DEVELOPER RELATED: + + - The RPM spec file was improved to install more useful files. + + - A few developer-oriented scripts were moved from the support dir + to the packaging dir. + + +NEWS for rsync 3.0.1 (3 Apr 2008) +Protocol: 30 (unchanged) +Changes since 3.0.0: + + NOTABLE CHANGES IN BEHAVIOR: + + - Added the 'c'-flag to the itemizing of non-regular files so that the + itemized output doesn't get hidden if there were no attribute changes, + and also so that the itemizing of a --copy-links run will distinguish + between copying an identical non-regular file and the creation of a + revised version with a new value (e.g. a changed symlink referent, a + new device number, etc.). + + BUG FIXES: + + - Fixed a crash bug when a single-use rsync daemon (via remote shell) was + run without specifying a --config=FILE option. + + - Fixed a crash when backing up a directory that has a default ACL. + + - Fixed a bug in the handling of xattr values that could cause rsync to + not think that a file's extended attributes are up-to-date. + + - Fixed the working of --fake-super with --link-dest and --xattrs. + + - Fixed a hang when combining --dry-run with --remove-source-files. + + - Fixed a bug with --iconv's handling of files that cannot be converted: + a failed name can no longer cause a transfer failure. + + - Fixed the building of the rounding.h file on systems that need custom + CPPFLAGS to be used. Also improved the error reporting if the building + of rounding.h fails. + + - Fixed the use of the --protect-args (-s) option when talking to a daemon. + + - Fixed the --ignore-existing option's protection of files on the receiver + that are non-regular files on the sender (e.g. if a symlink or a dir on + the sender is trying to replace a file on the receiver). The reverse + protection (protecting a dir/symlink/device from being replaced by a + file) was already working. + + - Fixed an assert failure if --hard-links is combined with an option that + can skip a file in a set of hard-linked files (i.e. --ignore-existing, + --append, etc.), without skipping all the files in the set. + + - Avoid setting the modify time on a directory that already has the right + modify time set. This avoids tweaking the dir's ctime. + + - Improved the daemon-exclude handling to do a better job of applying the + exclude rules to path entries. It also sends the user an error just as + if the files were actually missing (instead of silently ignoring the + user's args), and avoids sending the user the filter-action messages + for these non-user-initiated rules. + + - Fixed some glitches with the dry-run code's missing-directory + handling, including a problem when combined with --fuzzy. + + - Fixed some glitches with the skipped-directory handling. + + - Fixed the 'T'-flag itemizing of symlinks when --time isn't preserved. + + - Fixed a glitch in the itemizing of permissions with the -E option. + + - The --append option's restricting of transfers to those that add data no + longer prevents the updating of non-content changes to otherwise up-to- + date files (i.e. those with the same content but differing permissions, + ownership, xattrs, etc.). + + - Don't allow --fake-super to be specified with -XX (double --xattrs) + because the options conflict. If a daemon has "fake super" enabled, + it automatically downgrades a -XX request to -X. + + - Fixed a couple bugs in the parsing of daemon-config excludes that could + make a floating exclude rule get treated as matching an absolute path. + + - A daemon doesn't try to auto-refuse the "iconv" option if iconv-support + wasn't compiled in to the daemon (avoiding a warning in the logs). + + - Fixed the inclusion of per-dir merge files from implied dirs. + + - Fixed the support/rrsync script to work with the latest options that + rsync sends (including its flag-specifying use of -e to the server). + + ENHANCEMENTS: + + - Added the --old-dirs (--old-d) option to make it easier for a user to + ask for file-listings with older rsync versions (this is easier than + having to type "-r --exclude='/*/*'" manually). + + - When getting an error while asking an older rsync daemon for a file + listing, rsync will try to notice if the error is a rejection of the + --dirs (-d) option and let the user know how to work around the issue. + + - Added a few more --no-OPTION overrides. + + - Improved the documentation of the --append option. + + - Improved the documentation of the filter/exclude/include daemon + parameters. + + INTERNAL: + + - Fixed a couple minor bugs in the included popt library (ones which I + sent to the official popt project for inclusion in the 1.14 release). + + - Fixed a stat() call that should have been do_stat() so that the proper + normal/64-bit stat() function gets called. (Was in an area that should + not have caused problems, though.) + + - Changed the file-glob code to do a directory scan without using the + "glob" and "glob.h". This lets us do the globbing with less memory + churn, and also avoid adding daemon-excluded items to the returned + args. + + DEVELOPER RELATED: + + - The configure script tries to get the user's compiler to not warn about + unused function parameters if the build is not including one or more of + the ACL/xattrs/iconv features. + + - The configure script now has better checks for figuring out if the + included popt code should be used or not. + + - Fixed two testsuite glitches: avoid a failure if someone's "cd" command + outputs the current directory when cd-ing to a relative path, and made + the itemized test query how rsync was built to determine if it should + expect hard-linked symlinks or not. + + - Updated the testsuite to verify that various bug fixes remain fixed. + + - The RPM spec file was updated to have: (1) comments for how to use the + rsync-patch tar file, and (2) an /etc/xinetd.d/rsync file. + + - Updated the build scripts to work with a revised FTP directory + structure. + + +NEWS for rsync 3.0.0 (1 Mar 2008) +Protocol: 30 (changed) +Changes since 2.6.9: + + NOTABLE CHANGES IN BEHAVIOR: + + - The handling of implied directories when using --relative has changed to + send them as directories (e.g. no implied dir is ever sent as a symlink). + This avoids unexpected behavior and should not adversely affect most + people. If you're one of those rare individuals who relied upon having + an implied dir be duplicated as a symlink, you should specify the + transfer of the symlink and the transfer of the referent directory as + separate args. (See also --keep-dirlinks and --no-implied-dirs.) + Also, exclude rules no longer have a partial effect on implied dirs. + + - Requesting a remote file-listing without specifying -r (--recursive) now + sends the -d (--dirs) option to the remote rsync rather than sending -r + along with an extra exclude of /*/*. If the remote rsync does not + understand the -d option (i.e. it is 2.6.3 or older), you will need to + either turn off -d (--no-d), or specify -r --exclude='/*/*' manually. + + - In --dry-run mode, the last line of the verbose summary text is output + with a "(DRY RUN)" suffix to help remind you that no updates were made. + Similarly, --only-write-batch outputs "(BATCH ONLY)". + + - A writable rsync daemon with "use chroot" disabled now defaults to a + symlink-munging behavior designed to make symlinks safer while also + allowing absolute symlinks to be stored and retrieved. This also has + the effect of making symlinks unusable while they're in the daemon's + hierarchy. See the daemon's "munge symlinks" parameter for details. + + - Starting up an extra copy of an rsync daemon will not clobber the pidfile + for the running daemon -- if the pidfile exists, the new daemon will exit + with an error. This means that your wrapper script that starts the rsync + daemon should be made to handle lock-breaking (if you want any automatic + breaking of locks to be done). + + BUG FIXES: + + - A daemon with "use chroot = no" and excluded items listed in the daemon + config file now properly checks an absolute-path arg specified for these + options: --compare-dest, --link-dest, --copy-dest, --partial-dir, + --backup-dir, --temp-dir, and --files-from. + + - A daemon can now be told to disable all user- and group-name translation + on a per-module basis. This avoids a potential problem with a writable + daemon module that has "use chroot" enabled -- if precautions weren't + taken, a user could try to add a missing library and get rsync to use + it. This makes rsync safer by default, and more configurable when id- + translation is not desired. See the daemon's "numeric ids" parameter + for full details. + + - A chroot daemon can now indicate which part of its path should affect the + chroot call, and which part should become an inside-chroot path for the + module. This allows you to have outside-the-transfer paths (such as for + libraries) even when you enable chroot protection. The idiom used in the + rsyncd.conf file is: path = /chroot/dirs/./dirs/inside + + - If a file's data arrived successfully on the receiving side but the + rename of the temporary file to the destination file failed AND the + --remove-source-files (or the deprecated --remove-sent-files) option + was specified, rsync no longer erroneously removes the associated + source file. + + - Fixed the output of -ii when combined with one of the --*-dest options: + it now itemizes all the items, not just the changed ones. + + - Made the output of all file types consistent when using a --*-dest + option. Prior versions would output too many creation events for + matching items. + + - The code that waits for a child pid now handles being interrupted by a + signal. This fixes a problem with the pre-xfer exec function not being + able to get the exit status from the script. + + - A negated filter rule (i.e. with a '!' modifier) no longer loses the + negation when sending the filter rules to the remote rsync. + + - Fixed a problem with the --out-format (aka --log-format) option %f: it + no longer outputs superfluous directory info for a non-daemon rsync. + + - Fixed a problem with -vv (double --verbose) and --stats when "pushing" + files (which includes local copies). Version 2.6.9 would complete the + copy, but exit with an error when the receiver output its memory stats. + + - If --password-file is used on a non-daemon transfer, rsync now complains + and exits. This should help users figure out that they can't use this + option to control a remote shell's password prompt. + + - Make sure that directory permissions of a newly-created destination + directory are handled right when --perms is left off. + + - The itemized output of a newly-created destination directory is now + output as a creation event, not a change event. + + - Improved --hard-link so that more corner cases are handled correctly + when combined with options such as --link-dest and/or --ignore-existing. + + - The --append option no longer updates a file that has the same size. + + - Fixed a bug when combining --backup and --backup-dir with --inplace: + any missing backup directories are now created. + + - Fixed a bug when using --backup and --inplace with --whole-file or + --read-batch: backup files are actually created now. + + - The daemon pidfile is checked and created sooner in the startup sequence. + + - If a daemon module's "path" value is not an absolute pathname, the code + now makes it absolute internally (making it work properly). + + - Ensure that a temporary file always has owner-write permission while we + are writing to it. This avoids problems with some network filesystems + when transfering read-only files. + + - Any errors output about password-file reading no longer cause an error at + the end of the run about a partial transfer. + + - The --read-batch option for protocol 30 now ensures that several more + options are set correctly for the current batch file: --iconv, --acls, + --xattrs, --inplace, --append, and --append-verify. + + - Using --only-write-batch to a daemon receiver now works properly (older + versions would update some files while writing the batch). + + - Avoid outputting a "file has vanished" message when the file is a broken + symlink and --copy-unsafe-links or --copy-dirlinks is used (the code + already handled this for --copy-links). + + - Fixed the combination of --only-write-batch and --dry-run. + + - Fixed rsync's ability to remove files that are not writable by the file's + owner when rsync is running as the same user. + + - When transferring large files, the sender's hashtable of checksums is + kept at a more reasonable state of fullness (no more than 80% full) so + that the scanning of the hashtable will not bog down as the number of + blocks increases. + + ENHANCEMENTS: + + - A new incremental-recursion algorithm is now used when rsync is talking + to another 3.x version. This starts the transfer going more quickly + (before all the files have been found), and requires much less memory. + See the --recursive option in the manpage for some restrictions. + + - Lowered memory use in the non-incremental-recursion algorithm for typical + option values (usually saving from 21-29 bytes per file). + + - The default --delete algorithm is now --delete-during when talking to a + 3.x rsync. This is a faster scan than using --delete-before (which is + the default when talking to older rsync versions), and is compatible with + the new incremental recursion mode. + + - Rsync now allows multiple remote-source args to be specified rather than + having to rely on a special space-splitting side-effect of the remote- + shell. Additional remote args must specify the same host or an empty one + (e.g. empty: :file1 or ::module/file2). For example, this means that + local use of brace expansion now works: rsync -av host:dir/{f1,f2} . + + - Added the --protect-args (-s) option, that tells rsync to send most of + the command-line args at the start of the transfer rather than as args + to the remote-shell command. This protects them from space-splitting, + and only interprets basic wildcard special shell characters (*?[). + + - Added the --delete-delay option, which is a more efficient way to delete + files at the end of the transfer without needing a separate delete pass. + + - Added the --acls (-A) option to preserve Access Control Lists. This is + an improved version of the prior patch that was available, and it even + supports OS X ACLs. If you need to have backward compatibility with old, + ACL-patched versions of rsync, apply the acls.diff file from the patches + dir. + + - Added the --xattrs (-X) option to preserve extended attributes. This is + an improved version of the prior patch that was available, and it even + supports OS X xattrs (which includes their resource fork data). If you + need to have backward compatibility with old, xattr-patched versions of + rsync, apply the xattrs.diff file from the patches dir. + + - Added the --fake-super option that allows a non-super user to preserve + all attributes of a file by using a special extended-attribute idiom. + It even supports the storing of foreign ACL data on your backup server. + There is also an analogous "fake super" parameter for an rsync daemon. + + - Added the --iconv option, which allows rsync to convert filenames from + one character-set to another during the transfer. The default is to + make this feature available as long as your system has iconv_open(). + If compilation fails, specify --disable-iconv to configure, and then + rebuild. If you want rsync to perform character-set conversions by + default, you can specify --enable-iconv=CONVERT_STRING with the default + value for the --iconv option that you wish to use. For example, + "--enable-iconv=." is a good choice. See the rsync manpage for an + explanation of the --iconv option's settings. + + - A new daemon config parameter, "charset", lets you control the character- + set that is used during an --iconv transfer to/from a daemon module. You + can also set your daemon to refuse "no-iconv" if you want to force the + client to use an --iconv transfer (requiring an rsync 3.x client). + + - Added the --skip-compress=LIST option to override the default list of + file suffixes that will not be compressed when using --compress (-z). + + - The daemon's default for "dont compress" was extended to include: + *.7z *.mp[34] *.mov *.avi *.ogg *.jpg *.jpeg + The name-matching routine was also optimized to run more quickly. + + - The --max-delete option now outputs a warning if it skipped any file + deletions, including a count of how many deletions were skipped. (Older + versions just silently stopped deleting things.) + + - You may specify --max-delete=0 to a 3.0.0 client to request that it warn + about extraneous files without deleting anything. If you're not sure + what version the client is, you can use the less-obvious --max-delete=-1, + as both old and new versions will treat that as the same request (though + older versions don't warn). + + - The --hard-link option now uses less memory on both the sending and + receiving side for all protocol versions. For protocol 30, the use of a + hashtable on the sending side allows us to more efficiently convey to the + receiver what files are linked together. This reduces the amount of data + sent over the socket by a considerable margin (rather than adding more + data), and limits the in-memory storage of the device+inode information + to just the sending side for the new protocol 30, or to the receiving + side when speaking an older protocol (note that older rsync versions kept + the device+inode information on both sides). + + - The filter rules now support a perishable ("p") modifier that marks rules + that should not have an effect in a directory that is being deleted. e.g. + -f '-p .svn/' would only affect "live" .svn directories. + + - Rsync checks all the alternate-destination args for validity (e.g. + --link-dest). This lets the user know when they specified a directory + that does not exist. + + - If we get an ENOSYS error setting the time on a symlink, we don't + complain about it anymore (for those systems that even support the + setting of the modify-time on a symlink). + + - Protocol 30 now uses MD5 checksums instead of MD4. + + - Changed the --append option to not checksum the existing data in the + destination file, which speeds up file appending. + + - Added the --append-verify option, which works like the older --append + option (verifying the existing data in the destination file). For + compatibility with older rsync versions, any use of --append that is + talking protocol 29 or older will revert to the --append-verify method. + + - Added the --contimeout=SECONDS option that lets the user specify a + connection timeout for rsync daemon access. + + - Documented and extended the support for the RSYNC_CONNECT_PROG variable + that can be used to enhance the client side of a daemon connection. + + - Improved the dashes and double-quotes in the nroff manpage output. + + - Rsync now supports a lot more --no-OPTION override options. + + INTERNAL: + + - The file-list sorting algorithm now uses a sort that keeps any same- + named items in the same order as they were specified. This allows + rsync to always ensure that the first of the duplicates is the one + that will be included in the copy. The new sort is also faster + than the glibc version of qsort() and mergesort(). + + - Rsync now supports the transfer of 64-bit timestamps (time_t values). + + - Made the file-deletion code use a little less stack when recursing + through a directory hierarchy of extraneous files. + + - Fixed a build problem with older (2.x) versions of gcc. + + - Added some isType() functions that make dealing with signed characters + easier without forcing variables via casts. + + - Changed strcat/strcpy/sprintf function calls to use safer versions. + + - Upgraded the included popt version to 1.10.2 and improved its use of + string-handling functions. + + - Added missing prototypes for compatibility functions from the lib dir. + + - Configure determines if iconv() has a const arg, allowing us to avoid a + compiler warning. + + - Made the sending of some numbers more efficient for protocol 30. + + - Make sure that a daemon process doesn't mind if the client was weird and + omitted the --server option. + + - There are more internal logging categories available in protocol 30 than + the age-old FINFO and FERROR, including FERROR_XFER and FWARN. These new + categories allow some errors and warnings to go to stderr without causing + an erroneous end-of-run warning about some files not being able to be + transferred. + + - Improved the use of "const" on pointers. + + - Improved J.W.'s pool_alloc routines to add a way of incrementally freeing + older sections of a pool's memory. + + - The getaddrinfo.c compatibility code in the "lib" dir was replaced with + some new code (derived from samba, derived from PostgreSQL) that has a + better license than the old code. + + DEVELOPER RELATED: + + - Rsync is now licensed under the GPLv3 or later. + + - Rsync is now being maintained in a "git" repository instead of CVS + (though the old CVS repository still exists for historical access). + Several maintenance scripts were updated to work with git. + + - Generated files are no longer committed into the source repository. The + autoconf and autoheader commands are now automatically run during the + normal use of "configure" and "make". The latest dev versions of all + generated files can also be copied from the samba.org web site (see the + prepare-source script's fetch option). + + - The "patches" directory of diff files is now built from branches in the + rsync git repository (branch patch/FOO creates file patches/FOO.diff). + This directory is now distributed in a separate separate tar file named + rsync-patches-VERSION.tar.gz instead of the main rsync-VERSION.tar.gz. + + - The proto.h file is now built using a simple perl script rather than a + complex awk script, which proved to be more widely compatible. + + - When running the tests, we now put our per-test temp dirs into a sub- + directory named testtmp (which is created, if missing). This allows + someone to symlink the testtmp directory to another filesystem (which is + useful if the build dir's filesystem does not support ACLs and xattrs, + but another filesystem does). + + - Rsync now has a way of handling protocol-version changes during the + development of a new protocol version. This causes any out-of-sync + versions to speak an older protocol rather than fail in a cryptic manner. + This addition makes it safer to deploy a pre-release version that may + interact with the public. This new exchange of sub-version info does not + interfere with the {MIN,MAX}_PROTOCOL_VERSION checking algorithm (which + does not have enough range to allow the main protocol number to be + incremented for every minor tweak in that happens during development). + + - The csprotocol.txt file was updated to mention the daemon protocol change + in the 3.0.0 release. + + +NEWS for rsync 2.6.9 (6 Nov 2006) +Protocol: 29 (unchanged) +Changes since 2.6.8: + + BUG FIXES: + + - If rsync is interrupted via a handled signal (such as SIGINT), it will + once again clean-up its temp file from the destination dir. + + - Fixed an overzealous sanitizing bug in the handling of the --link-dest, + --copy-dest, and --compare-dest options to a daemon without chroot: if + the copy's destination dir is deeper than the top of the module's path, + these options now accept a safe number of parent-dir (../) references + (since these options are relative to the destination dir). The old code + incorrectly chopped off all "../" prefixes for these options, no matter + how deep the destination directory was in the module's hierarchy. + + - Fixed a bug where a deferred info/error/log message could get sent + directly to the sender instead of being handled by rwrite() in the + generator. This fixes an "unexpected tag 3" fatal error, and should + also fix a potential problem where a deferred info/error message from + the receiver might bypass the log file and get sent only to the client + process. (These problems could only affect an rsync daemon that was + receiving files.) + + - Fixed a bug when --inplace was combined with a --*-dest option and we + update a file's data using an alternate basis file. The code now + notices that it needs to copy the matching data from the basis file + instead of (wrongly) assuming that it was already present in the file. + + - Fixed a bug where using --dry-run with a --*-dest option with a path + relative to a directory that does not yet exist: the affected option + gets its proper path value so that the output of the dry-run is right. + + - Fixed a bug in the %f logfile escape when receiving files: the + destination path is now included in the output (e.g. you can now tell + when a user specifies a subdir inside a module). + + - If the receiving side fails to create a directory, it will now skip + trying to update everything that is inside that directory. + + - If --link-dest is specified with --checksum but without --times, rsync + will now allow a hard-link to be created to a matching link-dest file + even when the file's modify-time doesn't match the server's file. + + - The daemon now calls more timezone-using functions prior to doing a + chroot. This should help some C libraries to generate proper timestamps + from inside a chrooted daemon (and to not try to access /etc/timezone + over and over again). + + - Fixed a bug in the handling of an absolute --partial-dir=ABS_PATH option: + it now deletes an alternate basis file from the partial-dir that was used + to successfully update a destination file. + + - Fixed a bug in the handling of --delete-excluded when using a per-dir + merge file: the merge file is now honored on the receiving side, and + only its unqualified include/exclude commands are ignored (just as is + done for global include/excludes). + + - Fixed a recent bug where --delete was not working when transferring from + the root (/) of the filesystem with --relative enabled. + + - Fixed a recent bug where an --exclude='*' could affect the root (/) of + the filesystem with --relative enabled. + + - When --inplace creates a file, it is now created with owner read/write + permissions (0600) instead of no permissions at all. This avoids a + problem continuing a transfer that was interrupted (since --inplace + will not update a file that has no write permissions). + + - If either --remove-source-files or --remove-sent-files is enabled and we + are unable to remove the source file, rsync now outputs an error. + + - Fixed a bug in the daemon's "incoming chmod" rule: newly-created + directories no longer get the 'F' (file) rules applied to them. + + - Fixed an infinite loop bug when a filter rule was rejected due to being + overly long. + + - When the server receives a --partial-dir option from the client, it no + longer runs the client-side code that adds an assumed filter rule (since + the client will be sending us the rules in the usual manner, and they + may have chosen to override the auto-added rule). + + ENHANCEMENTS: + + - Added the --log-file=FILE and --log-file-format=FORMAT options. These + can be used to tell any rsync to output what it is doing to a log file. + They work with a client rsync, a non-daemon server rsync (see the man + page for instructions), and also allows the overriding of rsyncd.conf + settings when starting a daemon. + + - The --log-format option was renamed to be --out-format to avoid confusing + it with affecting the log-file output. (The old option remains as an + alias for the new to preserve backward compatibility.) + + - Made "log file" and "syslog facility" settable on a per-module basis in + the daemon's config file. + + - Added the --remove-source-files option as a replacement for the (now + deprecated) --remove-sent-files option. This new option removes all + non-dirs from the source directories, even if the file was already + up-to-date. This fixes a problem where interrupting an rsync that + was using --remove-sent-files and restarting it could leave behind + a file that the earlier rsync synchronized, but didn't get to remove. + (The deprecated --remove-sent-files is still understood for now, and + still behaves in the same way as before.) + + - Added the option --no-motd to suppress the message-of-the-day output + from a daemon when doing a copy. (See the manpage for a caveat.) + + - Added a new environment variable to the pre-/post-xfer exec commands (in + the daemon's config file): RSYNC_PID. This value will be the same in + both the pre- and post-xfer commands, so it can be used as a unique ID + if the pre-xfer command wants to cache some arg/request info for the + post-xfer command. + + INTERNAL: + + - Did a code audit using IBM's code-checker program and made several + changes, including: replacing most of the strcpy() and sprintf() + calls with strlcpy(), snprintf(), and memcpy(), adding a 0-value to + an enum that had been intermingling a literal 0 with the defined enum + values, silencing some uninitialized memory checks, marking some + functions with a "noreturn" attribute, and changing an "if" that + could never succeed on some platforms into a pre-processor directive + that conditionally compiles the code. + + - Fixed a potential bug in f_name_cmp() when both the args are a + top-level "." dir (which doesn't happen in normal operations). + + - Changed exit_cleanup() so that it can never return instead of exit. + The old code might return if it found the exit_cleanup() function + was being called recursively. The new code is segmented so that + any recursive calls move on to the next step of the exit-processing. + + - The macro WIFEXITED(stat) will now be defined if the OS didn't already + define it. + + DEVELOPER RELATED: + + - The acls.diff and xattrs.diff patches have received a bunch of work to + make them much closer to being acceptable in the main distribution. + The xattrs patch also has some preliminary Mac OS X and FreeBSD + compatibility code that various system types to exchange extended + file-attributes. + + - A new diff in the patches dir, fake-root.diff, allows rsync to + maintain a backup hierarchy with full owner, group, and device info + without actually running as root. It does this using a special + extended attribute, so it depends on xattrs.diff (which depends on + acls.diff). + + - The rsync.yo and rsyncd.conf.yo files have been updated to work + better with the latest yodl 2.x releases. + + - Updated config.guess and config.sub to their 2006-07-02 versions. + + - Updated various files to include the latest FSF address and to have + consistent opening comments. + + +NEWS for rsync 2.6.8 (22 Apr 2006) +Protocol: 29 (unchanged) +Changes since 2.6.7: + + BUG FIXES: + + - Fixed a bug in the exclude code where an anchored exclude without any + wildcards fails to match an absolute source arg, but only when --relative + is in effect. + + - Improved the I/O code for the generator to fix a potential hang when the + receiver gets an EOF on the socket but the generator's select() call + never indicates that the socket is writable for it to be notified about + the EOF. (This can happen when using stunnel). + + - Fixed a problem with the file-reading code where a failed read (such as + that caused by a bad sector) would not advance the file's read-position + beyond the failed read's data. + + - Fixed a logging bug where the "log file" directive was not being honored + in a single-use daemon (one spawned by a remote-shell connection or by + init). + + - If rsync cannot honor the --delete option, we output an error and exit + instead of silently ignoring the option. + + - Fixed a bug in the --link-dest code that prevented special files (such as + fifos) from being linked. + + - The ability to hard-link symlinks and special files is now determined at + configure time instead of at runtime. This fixes a bug with --link-dest + creating a hard-link to a symlink's referent on a BSD system. + + ENHANCEMENTS: + + - In daemon mode, if rsync fails to bind to the requested port, the + error(s) returned by socket() and/or bind() are now logged. + + - When we output a fatal error, we now output the version of rsync in the + message. + + - Improved the documentation for the --owner and --group options. + + - The rsyncstats script in "support" has an improved line-parsing regex + that is easier to read and also makes it to parse syslog-generated lines. + + - A new script in "support": file-attr-restore, can be used to restore the + attributes of a file-set (the permissions, ownership, and group info) + taken from the cached output of a "find ARG... -ls" command. + + DEVELOPER RELATED: + + - Removed the unused function write_int_named(), the unused variable + io_read_phase, and the rarely used variable io_write_phase. This also + elides the confusing 'phase "unknown"' part of one error message. + + - Removed two unused configure checks and two related (also unused) + compatibility functions. + + - The xattrs.diff patch received a security fix that prevents a potential + buffer overflow in the receive_xattr() code. + + - The acls.diff patch has been improved quite a bit, with more to come. + + - A new patch was added: log-file.diff. This contains an early version of + a future option, --log-file=FILE, that will allow any rsync to log its + actions to a file (something that only a daemon supports at present). + + +NEWS for rsync 2.6.7 (11 Mar 2006) +Protocol: 29 (unchanged) +Changes since 2.6.6: + + OUTPUT CHANGES: + + - The letter 'D' in the itemized output was being used for both devices + (character or block) as well as other special files (such as fifos and + named sockets). This has changed to separate non-device special files + under the 'S' designation (e.g. "cS+++++++ path/fifo"). See also the + "--specials" option, below. + + - The way rsync escapes unreadable characters has changed. First, rsync + now has support for recognizing valid multibyte character sequences in + your current locale, allowing it to escape fewer characters than before + for a locale such as UTF-8. Second, it now uses an escape idiom of + "\#123", which is the literal string "\#" followed by exactly 3 octal + digits. Rsync no longer doubles a backslash character in a filename + (e.g. it used to output "foo\\bar" when copying "foo\bar") -- now it only + escapes a backslash that is followed by a hash-sign and 3 digits (0-9) + (e.g. it will output "foo\#134#789" when copying "foo\#789"). See also + the --8-bit-output (-8) option, mentioned below. + + Script writers: the local rsync is the one that outputs escaped names, + so if you need to support unescaping of filenames for older rsyncs, I'd + suggest that you parse the output of "rsync --version" and only use the + old unescaping rules for 2.6.5 and 2.6.6. + + BUG FIXES: + + - Fixed a really old bug that caused --checksum (-c) to checksum all the + files encountered during the delete scan (ouch). + + - Fixed a potential hang in a remote generator: when the receiver gets a + read-error on the socket, it now signals the generator about this so that + the generator does not try to send any of the terminating error messages + to the client (avoiding a potential hang in some setups). + + - Made hard-links work with symlinks and devices again. + + - If the sender gets an early EOF reading a source file, we propagate this + error to the receiver so that it can discard the file and try requesting + it again (which is the existing behavior for other kinds of read errors). + + - If a device-file/special-file changes permissions, rsync now updates the + permissions without recreating the file. + + - If the user specifies a remote-host for both the source and destination, + we now output a syntax error rather than trying to open the destination + hostspec as a filename. + + - When --inplace creates a new destination file, rsync now creates it with + permissions 0600 instead of 0000 -- this makes restarting possible when + the transfer gets interrupted in the middle of sending a new file. + + - Reject the combination of --inplace and --sparse since the sparse-output + algorithm doesn't work when overwriting existing data. + + - Fixed the directory name in the error that is output when pop_dir() + fails. + + - Really fixed the parsing of a "!" entry in .cvsignore files this time. + + - If the generator gets a stat() error on a file, output it (this used to + require at least -vv for the error to be seen). + + - If waitpid() fails or the child rsync didn't exit cleanly, we now handle + the exit status properly and generate a better error. + + - Fixed some glitches in the double-verbose output when using --copy-dest, + --link-dest, or --compare-dest. Also improved how the verbose output + handles hard-links (within the transfer) that had an up-to-date alternate + "dest" file, and copied files (via --copy-dest). + + - Fixed the matching of the dont-compress items (e.g. *.gz) against files + that have a path component containing a slash. + + - If the code reading a filter/exclude file gets an EINTR error, rsync now + clears the error flag on the file handle so it can keep on reading. + + - If --relative is active, the sending side cleans up trailing "/" or "/." + suffixes to avoid triggering a bug in older rsync versions. Also, we now + reject a ".." dir if it would be sent as a relative dir. + + - If a non-directory is in the way of a directory and rsync is run with + --dry-run and --delete, rsync no longer complains about not being able + to opendir() the not-yet present directory. + + - When --list-only is used and a non-existent local destination dir was + also specified as a destination, rsync no longer generates a warning + about being unable to create the missing directory. + + - Fixed some problems with --relative --no-implied-dirs when the + destination directory did not yet exist: we can now create a symlink or + device when it is the first thing in the missing dir, and --fuzzy no + longer complains about being unable to open the missing dir. + + - Fixed a bug where the --copy-links option would not affect implied + directories without --copy-unsafe-links (see --relative). + + - Got rid of the need for --force to be used in some circumstances with + --delete-after (making it consistent with --delete-before/-during). + + - Rsync now ignores the SIGXFSZ signal, just in case your OS sends this + when a file is too large (rsync handles the write error). + + - Fixed a bug in the Proxy-Authorization header's base64-encoded value: it + was not properly padded with trailing '=' chars. This only affects a + user that need to use a password-authenticated proxy for an outgoing + daemon-rsync connection. + + - If we're transferring an empty directory to a new name, rsync no longer + forces S_IWUSR if it wasn't already set, nor does it accidentally leave + it set. + + - Fixed a bug in the debug output (-vvvvv) that could mention the wrong + checksum for the current file offset. + + - Rsync no longer allows a single directory to be copied over a non- + directory destination arg. + + ENHANCEMENTS: + + - Added the --append option that makes rsync append data onto files that + are longer on the source than the destination (this includes new files). + + - Added the --min-size=SIZE option to exclude small files from the + transfer. + + - Added the --compress-level option to allow you to set how aggressive + rsync's compression should be (this option implies --compress). + + - Enhanced the parsing of the SIZE value for --min-size and --max-size to + allow easy entry of multiples of 1000 (instead of just multiples of 1024) + and off-by-one values too (e.g. --max-size=8mb-1). + + - Added the --8-bit-output (-8) option, which tells rsync to avoid escaping + high-bit characters that it thinks are unreadable in the current locale. + + - The new option --human-readable (-h) changes the output of --progress, + --stats, and the end-of-run summary to be easier to read. If repeated, + the units become powers of 1024 instead of powers of 1000. (The old + meaning of -h, as a shorthand for --help, still works as long as you + just use it on its own, as in "rsync -h".) + + - If lutimes() and/or lchmod() are around, use them to allow the + preservation of attributes on symlinks. + + - The --link-dest option now affects symlinks and devices (when possible). + + - Added two config items to the rsyncd.conf parsing: "pre-xfer exec" and + "post-xfer exec". These allow a command to be specified on a per-module + basis that will be run before and/or after a daemon-mode transfer. (See + the man page for a list of the environment variables that are set with + information about the transfer.) + + - When using the --relative option, you can now insert a dot dir in + the source path to indicate where the replication of the source dirs + should start. For example, if you specify a source path of + rsync://host/module/foo/bar/./baz/dir with -R, rsync will now only + replicate the "baz/dir" part of the source path (note: a trailing + dot dir is unaffected unless it also has a trailing slash). + + - Added some new --no-FOO options that make it easier to override unwanted + implied or default options. For example, "-a --no-o" (aka "--archive + --no-owner") can be used to turn off the preservation of file ownership + that is implied by -a. + + - Added the --chmod=MODE option that allows the destination permissions to + be changed from the source permissions. E.g. --chmod=g+w,o-rwx + + - Added the "incoming chmod" and "outgoing chmod" daemon options that allow + a module to specify what permissions changes should be applied to all + files copied to and from the daemon. + + - Allow the --temp-dir option to be specified when starting a daemon, which + sets the default temporary directory for incoming files. + + - If --delete is combined with --dirs without --recursive, rsync will now + delete in any directory whose content is being synchronized. + + - If --backup is combined with --delete without --backup-dir (and without + --delete-excluded), we add a "protect" filter-rule to ensure that files + with the backup suffix are not deleted. + + - The file-count stats that are output by --progress were improved to + better indicate what the numbers mean. For instance, the output: + "(xfer#5, to-check=8383/9999)" indicates that this was the fifth file + to be transferred, and we still need to check 8383 more files out of + a total of 9999. + + - The include/exclude code now allows a dir/*** directive (with 3 trailing + stars) to match both the dir itself as well as all the content below the + dir (dir/** would not match the dir). + + - Added the --prune-empty-dirs (-m) option that makes the receiving rsync + discard empty chains of directories from the file-list. This makes it + easier to selectively copy files from a source hierarchy and end up with + just the directories needed to hold the resulting files. + + - If the --itemize-changes (-i) option is repeated, rsync now includes + unchanged files in the itemized output (similar to -vv, but without all + the other verbose messages that can get in the way). Of course, the + client must be version 2.6.7 for this to work, but the remote rsync only + needs to be 2.6.7 if you're pushing files. + + - Added the --specials option to tell rsync to copy non-device special + files (which rsync now attempts even as a normal user). The --devices + option now requests the copying of just devices (character and block). + The -D option still requests both (e.g. --devices and --specials), -a + still implies -D, and non-root users still get a silent downgrade that + omits device copying. + + - Added the --super option to make the receiver always attempt super-user + activities. This is useful for systems that allow things such as devices + to be created or ownership to be set without being UID 0, and is also + useful for someone who wants to ensure that errors will be output if the + receiving rsync isn't being run as root. + + - Added the --sockopts option for those few who want to customize the TCP + options used to contact a daemon rsync. + + - Added a way for the --temp-dir option to be combined with a partial-dir + setting that lets rsync avoid non-atomic updates (for those times when + --temp-dir is not being used because space is tight). + + - A new support script, files-to-excludes, will transform a list of files + into a set of include/exclude directives that will copy those files. + + - A new option, --executability (-E) can be used to preserve just the + execute bit on files, for those times when using the --perms option is + not desired. + + - The daemon now logs each connection and also each module-list request + that it receives. + + - New log-format options: %M (modtime), %U (uid), %G (gid), and %B + (permission bits, e.g. "rwxr-xrwt"). + + - The --dry-run option no longer forces the enabling of --verbose. + + - The --remove-sent-files option now does a better job of incrementally + removing the sent files on the sending side (older versions tended to + clump up all the removals at the end). + + - A daemon now supersedes its minimal SIGCHLD handler with the standard + PID-remembering version after forking. This ensures that the generator + can get the child-exit status from the receiver. + + - Use of the --bwlimit option no longer interferes with the remote rsync + sending error messages about invalid/refused options. + + - Rsync no longer returns a usage error when used with one local source arg + and no destination: this now implies the --list-only option, just like + the comparable situation with a remote source arg. + + - Added the --copy-dirlinks option, a more limited version of --copy-links. + + - Various documentation improvements, including: a better synopsis, some + improved examples, a better discussion of the presence and absence of + --perms (including how it interacts with the new --executability and + --chmod options), an extended discussion of --temp-dir, an improved + discussion of --partial-dir, a better description of rsync's pattern + matching characters, an improved --no-implied-dirs section, and the + documenting of what the --stats option outputs. + + - Various new and updated diffs in the patches dir, including: acls.diff, + xattrs.diff, atimes.diff, detect-renamed.diff, and slp.diff. + + INTERNAL: + + - We now use sigaction() and sigprocmask() if possible, and fall back on + signal() if not. Using sigprocmask() ensures that rsync enables all the + signals that it needs, just in case it was started in a masked state. + + - Some buffer sizes were expanded a bit, particularly on systems where + MAXPATHLEN is overly small (e.g. cygwin). + + - If io_printf() tries to format more data than fits in the buffer, exit + with an error instead of transmitting a truncated buffer. + + - If a va_copy macro is defined, lib/snprintf.c will use it when defining + the VA_COPY macro. + + - Reduced the amount of stack memory needed for each level of directory + recursion by nearly MAXPATHLEN bytes. + + - The wildmatch function was extended to allow an array of strings to be + supplied as the string to match. This allows the exclude code to do less + string copying. + + - Got rid of the safe_fname() function (and all the myriad calls) and + replaced it with a new function in the log.c code that filters all the + output going to the terminal. + + - Unified the f_name() and the f_name_to() functions. + + - Improved the hash-table code the sender uses to handle checksums to make + it use slightly less memory and run just a little faster. + + DEVELOPER RELATED: + + - The diffs in the patches dir now require "patch -p1 high in clean_flist() was wrong for an empty list. + This could cause flist_find() to crash in certain rare circumstances + (e.g. if just the right directory setup was around when --fuzzy was + combined with --link-dest). + + - The outputting of hard-linked files when verbosity was > 1 was not right: + (1) Without -i it would output the name of each hard-linked file as + though it had been changed; it now outputs a "is hard linked" message for + the file. (2) With -i it would output all dots for the unchanged + attributes of a hard-link; it now changes those dots to spaces, as is + done for other totally unchanged items. + + - When backing up a changed symlink or device, get rid of any old backup + item so that we don't get an "already exists" error. + + - A couple places that were comparing a local and a remote modification- + time were not honoring the --modify-window option. + + - Fixed a bug where the 'p' (permissions) itemized-changes flag might get + set too often (if some non-significant mode bits differed). + + - Fixed a really old, minor bug that could cause rsync to warn about being + unable to mkdir() a path that ends in "/." because it just created the + directory (required --relative, --no-implied-dirs, a source path that + ended in either a trailing slash or a trailing "/.", and a non-existing + destination dir to tickle the bug in a recent version). + + ENHANCEMENTS: + + - Made the "max verbosity" setting in the rsyncd.conf file settable on a + per-module basis (which now matches the documentation). + + - The support/rrsync script has been upgraded to verify the args of options + that take args (instead of rejecting any such options). The script was + also changed to try to be more secure and to fix a problem in the parsing + of a pull operation that has multiple sources. + + - Improved the documentation that explains the difference between a + normal daemon transfer and a daemon-over remote-shell transfer. + + - Some of the diffs supplied in the patches dir were fixed and/or + improved. + + BUILD CHANGES: + + - Made configure define NOBODY_USER (currently hard-wired to "nobody") and + NOBODY_GROUP (set to either "nobody" or "nogroup" depending on what we + find in the /etc/group file). + + - Added a test to the test suite, itemized.test, that tests the output of + -i (log-format w/%i) and some double-verbose messages. + + +NEWS for rsync 2.6.5 (1 Jun 2005) +Protocol: 29 (unchanged) +Changes since 2.6.4: + + OUTPUT CHANGES: + + - Non-printable chars in filenames are now output using backslash- + escaped characters rather than '?'s. Any non-printable character is + output using 3 digits of octal (e.g. "\n" -> "\012"), and a backslash + is now output as "\\". Rsync also uses your locale setting, which + can make it treat fewer high-bit characters as non-printable. + + - If rsync received an empty file-list when pulling files, it would + output a "nothing to do" message and exit with a 0 (success) exit + status, even if the remote rsync returned an error (it did not do + this under the same conditions when pushing files). This was changed + to make the pulling behavior the same as the pushing behavior: we + now do the normal end-of-run outputting (depending on options) and + exit with the appropriate exit status. + + BUG FIXES: + + - A crash bug was fixed when a daemon had its "path" set to "/", did + not have chroot enabled, and used some anchored excludes in the + rsyncd.conf file. + + - Fixed a bug in the transfer of a single file when -H is specified + (rsync would either infinite loop or perhaps crash). + + - Fixed a case where the generator might try (and fail) to tweak the + write-permissions of a read-only directory in list-only mode (this + only caused an annoying warning message). + + - If --compare-dest or --link-dest uses a locally-copied file as the + basis for an updated version, log this better when --verbose or -i + is in effect. + + - Fixed the accidental disabling of --backup during the --delete-after + processing. + + - Restored the ability to use the --address option in client mode (in + addition to its use in daemon mode). + + - Make sure that some temporary progress information from the delete + processing does not get left on the screen when it is followed by a + newline. + + - When --existing skips a directory with extra verbosity, refer to it + as a "directory", not a "file". + + - When transferring a single file to a different-named file, any + generator messages that are source-file related no longer refer to + the file by the destination filename. + + - Fixed a bug where hard-linking a group of files might fail if the + generator hasn't created a needed destination directory yet. + + - Fixed a bug where a hard-linked group of files that is newly-linked + to a file in a --link-dest dir doesn't link the files from the rest + of the cluster. + + - When deleting files with the --one-file-system (-x) option set, rsync + no longer tries to remove files from inside a mount-point on the + receiving side. Also, we don't complain about being unable to remove + the mount-point dir. + + - Fixed a compatibility problem when using --cvs-ignore (-C) and + sending files to an older rsync without using --delete. + + - Make sure that a "- !" or "+ !" include/exclude pattern does not + trigger the list-clearing action that is reserved for "!". + + - Avoid a timeout in the generator when the sender/receiver aren't + handling the generator's checksum output quickly enough. + + - Fixed the omission of some directories in the delete processing when + --relative (-R) was combined with a source path that had a trailing + slash. + + - Fixed a case where rsync would erroneously delete some files and then + re-transfer them when the options --relative (-R) and --recursive + (-r) were both enabled (along with --delete) and a source path had a + trailing slash. + + - Make sure that --max-size doesn't affect a device or a symlink. + + - Make sure that a system with a really small MAXPATHLEN does not cause + the buffers in readfd_unbuffered() to be too small to receive normal + messages. (This mainly affected Cygwin.) + + - If a source pathname ends with a filename of "..", treat it as if + "../" had been specified (so that we don't copy files to the parent + dir of the destination). + + - If --delete is combined with a file-listing rsync command (i.e. no + transfer is happening), avoid outputting a warning that we couldn't + delete anything. + + - If --stats is specified with --delete-after, ensure that all the + "deleting" messages are output before the statistics. + + - Improved one "if" in the deletion code that was only checking errno + for ENOTEMPTY when it should have also been checking for EEXIST (for + compatibility with OS variations). + + ENHANCEMENTS: + + - Added the --only-write-batch=FILE option that may be used (instead + of --write-batch=FILE) to create a batch file without doing any + actual updating of the destination. This allows you to divert all + the file-updating data away from a slow data link (as long as you + are pushing the data to the remote server when creating the batch). + + - When the generator is taking a long time to fill up its output buffer + (e.g. if the transferred files are few, small, or missing), it now + periodically flushes the output buffer so that the sender/receiver + can get started on the files sooner rather than later. + + - Improved the keep-alive code to handle a long silence between the + sender and the receiver that can occur when the sender is receiving + the checksum data for a large file. + + - Improved the auth-errors that are logged by the daemon to include + some information on why the authorization failed: wrong user, + password mismatch, etc. (The client-visible message is unchanged!) + + - Improved the client's handling of an "@ERROR" from a daemon so that + it does not complain about an unexpectedly closed socket (since we + really did expect the socket to close). + + - If the daemon can't open the log-file specified in rsyncd.conf, fall + back to using syslog and log an appropriate warning. This is better + than what was typically a totally silent (and fatal) failure (since a + daemon is not usually run with the --no-detach option that was + necessary to see the error on stderr). + + - The man pages now consistently refer to an rsync daemon as a "daemon" + instead of a "server" (to distinguish it from the server process in a + non-daemon transfer). + + - Made a small change to the rrsync script (restricted rsync -- in the + support dir) to make a read-only server reject all --remove-* options + when sending files (to future-proof it against the possibility of + other similar options being added at some point). + + INTERNAL: + + - Rsync now calls setlocale(LC_CTYPE, ""). This enables isprint() to + better discern which filename characters need to be escaped in + messages (which should result in fewer escaped characters in some + locales). + + - Improved the naming of the log-file open/reopen/close functions. + + - Removed some protocol-compatibility code that was only needed to help + someone running a pre-release of 2.6.4. + + BUILD CHANGES: + + - Added configure option "--disable-locale" to disable any use of + setlocale() in the binary. + + - Fixed a bug in the SUPPORT{,_HARD}_LINKS #defines which prevented + rsync from being built without symlink or hard-link support. + + - Only #define HAVE_REMSH if it is going to be set to 1. + + - Configure now disables the use of mkstemp() under HP-UX (since they + refuse to fix its broken handling of large files). + + - Configure now explicitly checks for the lseek64() function so that + the code can use HAVE_LSEEK64 instead of inferring lseek64()'s + presence based on the presence of the off64_t type. + + - Configure no longer mentions the change in the default remote-shell + (from rsh to ssh) that occurred for the 2.6.0 release. + + - Some minor enhancements to the test scripts. + + - Added a few new *.diff files to the patches dir, including a patch + that enables the optional copying of extended attributes. + + +NEWS for rsync 2.6.4 (30 March 2005) +Protocol: 29 (changed) +Changes since 2.6.3: + + OUTPUT CHANGES: + + - When rsync deletes a directory and outputs a verbose message about + it, it now appends a trailing slash to the name instead of (only + sometimes) outputting a preceding "directory " string. + + - The --stats output will contain file-list time-statistics if both + sides are 2.6.4, or if the local side is 2.6.4 and the files are + being pushed (since the stats come from the sending side). + (Requires protocol 29 for a pull.) + + - The "%o" (operation) log-format escape now has a third value (besides + "send" and "recv"): "del." (with trailing dot to make it 4 chars). + This changes the way deletions are logged in the daemon's log file. + + - When the --log-format option is combined with --verbose, rsync now + avoids outputting the name of the file twice in most circumstances. + As long as the --log-format item does not refer to any post-transfer + items (such as %b or %c), the --log-format message is output prior to + the transfer, so --verbose is now the equivalent of a --log-format of + '%n%L' (which outputs the name and any link info). If the log output + must occur after the transfer to be complete, the only time the name + is also output prior to the transfer is when --progress was specified + (so that the name will precede the progress stats, and the full + --log-format output will come after). + + - Non-printable characters in filenames are replaced with a '?' to + avoid corrupting the screen or generating empty lines in the output. + + BUG FIXES: + + - Restore the list-clearing behavior of "!" in a .cvsignore file (2.6.3 + was only treating it as a special token in an rsync include/exclude + file). + + - The combination of --verbose and --dry-run now mentions the full list + of changes that would be output without --dry-run. + + - Avoid a mkdir warning when removing a directory in the destination + that already exists in the --backup-dir. + + - An OS that has a binary mode for its files (such as cygwin) needed + setmode(fd, O_BINARY) called on the temp-file we opened with + mkstemp(). (Fix derived from cygwin's 2.6.3 rsync package.) + + - Fixed a potential hang when verbosity is high, the client side is + the sender, and the file-list is large. + + - Fixed a potential protocol-corrupting bug where the generator could + merge a message from the receiver into the middle of a multiplexed + packet of data if only part of that data had been written out to the + socket when the message from the generator arrived. + + - We now check if the OS doesn't support using mknod() for creating + FIFOs and sockets, and compile-in some compatibility code using + mkfifo() and socket() when necessary. + + - Fixed an off-by-one error in the handling of --max-delete=N. Also, + if the --max-delete limit is exceeded during a run, we now output a + warning about this at the end of the run and exit with a new error + code (25). + + - One place in the code wasn't checking if fork() failed. + + - The "ignore nonreadable" daemon parameter used to erroneously affect + readable symlinks that pointed to a non-existent file. + + - If the OS does not have lchown() and a chown() of a symlink will + affect the referent of a symlink (as it should), we no longer try + to set the user and group of a symlink. + + - The generator now properly runs the hard-link loop and the dir-time + rewriting loop after we're sure that the redo phase is complete. + + - When --backup was specified with --partial-dir=DIR, where DIR is a + relative path, the backup code was erroneously trying to backup a + file that was put into the partial-dir. + + - If a file gets resent in a single transfer and the --backup option is + enabled along with --inplace, rsync no longer performs a duplicate + backup (it used to overwrite the first backup with the failed file). + + - One call to flush_write_file() was not being checked for an error. + + - The --no-relative option was not being sent from the client to a + server sender. + + - If an rsync daemon specified "dont compress = ..." for a file and the + client tried to specify --compress, the libz code was not handling a + compression level of 0 properly. This could cause a transfer failure + if the block-size for a file was large enough (e.g. rsync might have + exited with an error for large files). + + - Fixed a bug that would sometimes surface when using --compress and + sending a file with a block-size larger than 64K (either manually + specified, or computed due to the file being really large). Prior + versions of rsync would sometimes fail to decompress the data + properly, and thus the transferred file would fail its verification. + + - If a daemon can't open the specified log file (i.e. syslog is not + being used), die without crashing. We also output an error about + the failure on stderr (which will only be seen if --no-detach was + specified) and exit with a new error code (6). + + - A local transfer no longer duplicates all its include/exclude options + (since the forked process already has a copy of the exclude list, + there's no need to send them a set of duplicates). + + - The output of the items that are being updated by the generator (dirs, + symlinks, devices) is now intermingled in the proper order with the + output from the items that the receiver is updating (regular files) + when pulling. This misordering was particularly bad when --progress + was specified. (Requires protocol 29.) + + - When --timeout is specified, lulls that occur in the transfer while + the generator is doing work that does not generate socket traffic + (looking for changed files, deleting files, doing directory-time + touch-ups, etc.) will cause a new keep-alive packet to be sent that + should keep the transfer going as long as the generator continues to + make progress. (Requires protocol 29.) + + - The stat size of a device is not added to the total file size of the + items in the transfer (the size might be undefined on some OSes). + + - Fixed a problem with refused-option messages sometimes not making it + back to the client side when a remote --files-from was in effect and + the daemon was the receiver. + + - The --compare-dest option was not updating a file that differed in + (the preserved) attributes from the version in the compare-dest DIR. + + - When rsync is copying files into a write-protected directory, fixed + the change-report output for the directory so that we don't report + an identical directory as changed. + + ENHANCEMENTS: + + - Rsync now supports popt's option aliases, which means that you can + use /etc/popt and/or ~/.popt to create your own option aliases. + + - Added the --delete-during (--del) option which will delete files + from the receiving side incrementally as each directory in the + transfer is being processed. This makes it more efficient than the + default, before-the-transfer behavior, which is now also available as + --delete-before (and is still the default --delete-WHEN option that + will be chosen if --delete or --delete-excluded is specified without + a --delete-WHEN choice). All the --del* options infer --delete, so + an rsync daemon that refuses "delete" will still refuse to allow any + file-deleting options (including the new --remove-sent-files option). + + - All the --delete-WHEN options are now more memory efficient: + Previously an duplicate set of file-list objects was created on the + receiving side for the entire destination hierarchy. The new + algorithm only creates one directory of objects at a time (for files + inside the transfer). + + - Added the --copy-dest option, which works like --link-dest except + that it locally copies identical files instead of hard-linking them. + + - Added support for specifying multiple --compare-dest, --copy-dest, or + --link-dest options, but only of a single type. (Promoted from the + patches dir and enhanced.) (Requires protocol 29.) + + - Added the --max-size option. (Promoted from the patches dir.) + + - The daemon-mode options are now separated from the normal rsync + options so that they can't be mixed together. This makes it + impossible to start a daemon that has improper default option values + (which could cause problems when a client connects, such as hanging + or crashing). + + - The --bwlimit option may now be used in combination with --daemon + to specify both a default value for the daemon side and a value + that cannot be exceeded by a user-specified --bwlimit option. + + - Added the "port" parameter to the rsyncd.conf file. (Promoted from + the patches dir.) Also added "address". The command-line options + take precedence over a config-file option, as expected. + + - In _exit_cleanup(): when we are exiting with a partially-received + file, we now flush any data in the write-cache before closing the + partial file. + + - The --inplace support was enhanced to work with --compare-dest, + --link-dest, and (the new) --copy-dest options. (Requires protocol + 29.) + + - Added the --dirs (-d) option for an easier way to copy directories + without recursion. Any directories that are encountered are created + on the destination. Specifying a directory with a trailing slash + copies its immediate contents to the destination. + + - The --files-from option now implies --dirs (-d). + + - Added the --list-only option, which is mainly a way for the client to + put the server into listing mode without needing to resort to any + internal option kluges (e.g. the age-old use of "-r --exclude="/*/*" + for a non-recursive listing). This option is used automatically + (behind the scenes) when a modern rsync speaks to a modern daemon, + but may also be specified manually if you want to force the use of + the --list-only option over a remote-shell connection. + + - Added the --omit-dir-times (-O) option, which will avoid updating + the modified time for directories when --times was specified. This + option will avoid an extra pass through the file-list at the end of + the transfer (to tweak all the directory times), which may provide + an appreciable speedup for a really large transfer. (Promoted from + the patches dir.) + + - Added the --filter (-f) option and its helper option, -F. Filter + rules are an extension to the existing include/exclude handling + that also supports nested filter files as well as per-directory + filter files (like .cvsignore, but with full filter-rule parsing). + This new option was chosen in order to ensure that all existing + include/exclude processing remained 100% compatible with older + versions. Protocol 29 is needed for full filter-rule support, but + backward-compatible rules work with earlier protocol versions. + (Promoted from the patches dir and enhanced.) + + - Added the --delay-updates option that puts all updated files into + a temporary directory (by default ".~tmp~", but settable via the + --partial-dir=DIR option) until the end of the transfer. This + makes the updates a little more atomic for a large transfer. + + - If rsync is put into the background, any output from --progress is + reduced. + + - Documented the "max verbosity" setting for rsyncd.conf. (This + setting was added a couple releases ago, but left undocumented.) + + - The sender and the generator now double-check the file-list index + they are given, and refuse to try to do a file transfer on a + non-file index (since that would indicate that something had gone + very wrong). + + - Added the --itemize-changes (-i) option, which is a way to output a + more detailed list of what files changed and in what way. The effect + is the same as specifying a --log-format of "%i %n%L" (see both the + rsync and rsyncd.conf manpages). Works with --dry-run too. + + - Added the --fuzzy (-y) option, which attempts to find a basis file + for a file that is being created from scratch. The current algorithm + only looks in the destination directory for the created file, but it + does attempt to find a match based on size/mod-time (in case the file + was renamed with no other changes) as well as based on a fuzzy + name-matching algorithm. This option requires protocol 29 because it + needs the new file-sorting order. (Promoted from patches dir and + enhanced.) (Requires protocol 29.) + + - Added the --remove-sent-files option, which lets you move files + between systems. + + - The hostname in HOST:PATH or HOST::PATH may now be an IPv6 literal + enclosed in '[' and ']' (e.g. "[::1]"). (We already allowed IPv6 + literals in the rsync://HOST:PORT/PATH format.) + + - When rsync recurses to build the file list, it no longer keeps open + one or more directory handles from the dir's parent dirs. + + - When building under windows, the default for --daemon is now to + avoid detaching, requiring the new --detach option to force rsync + to detach. + + - The --dry-run option can now be combined with either --write-batch or + --read-batch, allowing you to run a do-nothing test command to see + what would happen without --dry-run. + + - The daemon's "read only" config item now sets an internal read_only + variable that makes extra sure that no write/delete calls on the + read-only side can succeed. + + - The log-format % escapes can now have a numeric field width in + between the % and the escape letter (e.g. "%-40n %08p"). + + - Improved the option descriptions in the --help text. + + SUPPORT FILES: + + - Added atomic-rsync to the support dir: a perl script that will + transfer some files using rsync, and then move the updated files into + place all at once at the end of the transfer. Only works when + pulling, and uses --link-dest and a parallel hierarchy of files to + effect its update. + + - Added mnt-excl to the support dir: a perl script that takes the + /proc/mounts file and translates it into a set of excludes that will + exclude all mount points (even mapped mounts to the same disk). The + excludes are made relative to the specified source dir and properly + anchored. + + - Added savetransfer.c to the support dir: a C program that can make + a copy of all the data that flows over the wire. This lets you test + for data corruption (by saving the data on both the sending side and + the receiving side) and provides one way to debug a protocol error. + + - Added rrsync to the support dir: this is an updated version of Joe + Smith's restricted rsync perl script. This helps to ensure that only + certain rsync commands can be run by an ssh invocation. + + INTERNAL: + + - Added better checking of the checksum-header values that come over + the socket. + + - Merged a variety of file-deleting functions into a single function so + that it is easier to maintain. + + - Improved the type of some variables (particularly blocksize vars) for + consistency and proper size. + + - Got rid of the uint64 type (which we didn't need). + + - Use a slightly more compatible set of core #include directives. + + - Defined int32 in a way that ensures that the build dies if we can't + find a variable with at least 32 bits. + + PROTOCOL DIFFERENCES FOR VERSION 29: + + - A 16-bit flag-word is transmitted after every file-list index. This + indicates what is changing between the sender and the receiver. The + generator now transmits an index and a flag-word to indicate when + dirs and symlinks have changed (instead of producing a message), + which makes the outputting of the information more consistent and + less prone to screen corruption (because the local receiver/sender is + now outputting all the file-change info messages). + + - If a file is being hard-linked, the ITEM_XNAME_FOLLOWS bit is enabled + in the flag-word and the name of the file that was linked immediately + follows in vstring format (see below). + + - If a file is being transferred with an alternate-basis file, the + ITEM_BASIS_TYPE_FOLLOWS bit is enabled in the flag-word and a single + byte follows, indicating what type of basis file was chosen. If that + indicates that a fuzzy-match was selected, the ITEM_XNAME_FOLLOWS bit + is set in the flag-word and the name of the match in vstring format + follows the basis byte. A vstring is a variable length string that + has its size written prior to the string, and no terminating null. + If the string is from 1-127 bytes, the length is a single byte. If + it is from 128-32767 bytes, the length is written as ((len >> 8) | + 0x80) followed by (len % 0x100). + + - The sending of exclude names is done using filter-rule syntax. This + means that all names have a prefixed rule indicator, even excludes + (which used to be sent as a bare pattern, when possible). The -C + option will include the per-dir .cvsignore merge file in the list of + filter rules so it is positioned correctly (unlike in some older + transfer scenarios). + + - Rsync sorts the filename list in a different way: it sorts the subdir + names after the non-subdir names for each dir's contents, and it + always puts a dir's contents immediately after the dir's name in the + list. (Previously an item named "foo.txt" would sort in between + directory "foo/" and "foo/bar".) + + - When talking to a protocol 29 rsync daemon, a list-only request + is able to note this before the options are sent over the wire and + the new --list-only option is included in the options. + + - When the --stats bytes are sent over the wire (or stored in a batch), + they now include two elapsed-time values: one for how long it took to + build the file-list, and one for how long it took to send it over the + wire (each expressed in thousandths of a second). + + - When --delete-excluded is specified with some filter rules (AKA + excludes), a client sender will now initiate a send of the rules to + the receiver (older protocols used to omit the sending of excludes in + this situation since there were no receiver-specific rules that + survived --delete-excluded back then). Note that, as with all the + filter-list sending, only items that are significant to the other + side will actually be sent over the wire, so the filter-rule list + that is sent in this scenario is often empty. + + - An index equal to the file-list count is sent as a keep-alive packet + from the generator to the sender, which then forwards it on to the + receiver. This normally invalid index is only a valid keep-alive + packet if the 16-bit flag-word that follows it contains a single bit + (ITEM_IS_NEW, which is normally an illegal flag to appear alone). + + - A protocol-29 batch file includes a bit for the setting of the --dirs + option and for the setting of the --compress option. Also, the shell + script created by --write-batch will use the --filter option instead + of --exclude-from to capture any filter rules. + + BUILD CHANGES: + + - Handle an operating system that use mkdev() in place of makedev(). + + - Improved configure to better handle cross-compiling. + + +NEWS for rsync 2.6.3 (30 Sep 2004) +Protocol: 28 (unchanged) +Changes since 2.6.2: + + SECURITY FIXES: + + - A bug in the sanitize_path routine (which affects a non-chrooted + rsync daemon) could allow a user to craft a pathname that would get + transformed into an absolute path for certain options (but not for + file-transfer names). If you're running an rsync daemon with chroot + disabled, *please upgrade*, ESPECIALLY if the user privs you run + rsync under is anything above "nobody". + + OUTPUT CHANGES (ATTN: those using a script to parse the verbose output): + + - Please note that the 2-line footer (output when verbose) now uses the + term "sent" instead of "wrote" and "received" instead of "read". If + you are not parsing the numeric values out of this footer, a script + would be better off using the empty line prior to the footer as the + indicator that the verbose output is over. + + - The output from the --stats option was similarly affected to change + "written" to "sent" and "read" to "received". + + - Rsync ensures that a filename that contains a newline gets mentioned + with each newline transformed into a question mark (which prevents a + filename from causing an empty line to be output). + + - The "backed up ..." message that is output when at least 2 --verbose + options are specified is now the same both with and without the + --backup-dir option. + + BUG FIXES: + + - Fixed a crash bug that might appear when --delete was used and + multiple source directories were specified. + + - Fixed a 32-bit truncation of the file length when generating the + checksums. + + - The --backup code no longer attempts to create some directories + over and over again (generating warnings along the way). + + - Fixed a bug in the reading of the secrets file (by the daemon) and + the password file (by the client): the files no longer need to be + terminated by a newline for their content to be read in. + + - If a file has a read error on the sending side or the reconstructed + data doesn't match the expected checksum (perhaps due to the basis + file changing during the transfer), the receiver will no longer + retain the resulting file unless the --partial option was specified. + (Note: for the read-error detection to work, neither side can be + older than 2.6.3 -- older receivers will always retain the file, and + older senders don't tell the receiver that the file had a read + error.) + + - If a file gets resent in a single transfer and the --backup option + is enabled, rsync no longer performs a duplicate backup (it used to + overwrite the original file in the backup area). + + - Files specified in the daemon's "exclude" or "exclude from" config + items are now excluded from being uploaded (assuming that the module + allows uploading at all) in addition to the old download exclusion. + + - Got rid of a potential hang in the receiver when near the end of a + phase. + + - When using --backup without a --backup-dir, rsync no longer preserves + the modify time on directories. This avoids confusing NFS. + + - When --copy-links (-L) is specified, we now output a separate error + for a symlink that has no referent instead of claiming that a file + "vanished". + + - The --copy-links (-L) option no longer has the side-effect of telling + the receiving side to follow symlinks. See the --keep-dirlinks + option (mentioned below) for a way to specify that behavior. + + - Error messages from the daemon server's option-parsing (such as + refused options) are now successfully transferred back to the client + (the server used to fail to send the message because the socket + wasn't in the right state for the message to get through). + + - Most transfer errors that occur during a daemon transfer are now + returned to the user in addition to being logged (some messages are + intended to be daemon-only and are not affected by this). + + - Fixed a bug in the daemon authentication code when using one of the + batch-processing options. + + - We try to work around some buggy IPv6 implementations that fail to + implement IPV6_V6ONLY. This should fix the "address in use" error + that some daemons get when running on an OS with a buggy IPv6 + implementation. Also, if the new code gets this error, we might + suggest that the user specify --ipv4 or --ipv6 (if we think it will + help). + + - When the remote rsync dies, make a better effort to recover any error + messages it may have sent before dying (the local rsync used to just + die with a socket-write error). + + - When using --delete and a --backup-dir that contains files that are + hard-linked to their destination equivalents, rsync now makes sure + that removed files really get removed (avoids a really weird rename() + behavior). + + - Avoid a bogus run-time complaint about a lack of 64-bit integers when + the int64 type is defined as an off_t and it actually has 64-bits. + + - Added a configure check for open64() without mkstemp64() so that we + can avoid using mkstemp() when such a combination is encountered. + This bypasses a problem writing out large temp files on OSes such as + AIX and HP-UX. + + - Fixed an age-old crash problem with --read-batch on a local copy + (rsync was improperly assuming --whole-file for the local copy). + + - When --dry-run (-n) is used and the destination directory does not + exist, rsync now produces a correct report of files that would be + sent instead of dying with a chdir() error. + + - Fixed a bug that could cause a slow-to-connect rsync daemon to die + with an error instead of waiting for the connection to finish. + + - Fixed an ssh interaction that could cause output to be lost when the + user chose to combine the output of rsync's stdout and stderr (e.g. + using the "2>&1"). + + - Fixed an option-parsing bug when --files-from got passed to a daemon. + + ENHANCEMENTS: + + - Added the --partial-dir=DIR option that lets you specify where to + (temporarily) put a partially transferred file (instead of over- + writing the destination file). E.g. --partial-dir=.rsync-partial + Also added support for the RSYNC_PARTIAL_DIR environment variable + that, when found, transforms a regular --partial option (such as + the convenient -P option) into one that also specifies a directory. + + - Added --keep-dirlinks (-K), which allows you to symlink a directory + onto another partition on the receiving side and have rsync treat it + as matching a normal directory from the sender. + + - Added the --inplace option that tells rsync to write each destination + file without using a temporary file. The matching of existing data + in the destination file can be severely limited by this, but there + are also cases where this is more efficient (such as appending data). + Use only when needed (see the man page for more details). + + - Added the "write only" option for the daemon's config file. + + - Added long-option names for -4 and -6 (namely --ipv4 and --ipv6) + and documented all these options in the man page. + + - Improved the handling of the --bwlimit option so that it's less + bursty, more accurate, and works properly over a larger range of + values. + + - The rsync daemon-over-ssh code now looks for SSH_CONNECTION and + SSH2_CLIENT in addition to SSH_CLIENT to figure out the IP address. + + - Added the --checksum-seed=N option for advanced users. + + - Batch writing/reading has a brand-new implementation that is simpler, + fixes a few weird problems with the old code (such as no longer + sprinkling the batch files into different dirs or even onto different + systems), and is much less intrusive into the code (making it easier + to maintain for the future). The new code generates just one data + file instead of three, which makes it possible to read the batch on + stdin via a remote shell. Also, the old requirement of forcing the + same fixed checksum-seed for all batch processing has been removed. + + - If an rsync daemon has a module set with "list = no" (which hides its + presence in the list of available modules), a user that fails to + authenticate gets the same "unknown module" error that they would get + if the module were actually unknown (while still logging the real + error to the daemon's log file). This prevents fishing for module + names. + + - The daemon's "refuse options" config item now allows you to match + option names using wildcards and/or the single-letter option names. + + - Each transferred file now gets its permissions and modified-time + updated before the temp-file gets moved into place. Previously, the + finished file would have a very brief window where its permissions + disallowed all group and world access. + + - Added the ability to parse a literal IPv6 address in an "rsync:" URL + (e.g. rsync://[2001:638:500:101::21]:873/module/dir). + + - The daemon's wildcard expanding code can now handle more than 1000 + filenames (it's now limited by memory instead of having a hard-wired + limit). + + INTERNAL: + + - Some cleanup in the exclude code has saved some per-exclude memory + and made the code easier to maintain. + + - Improved the argv-overflow checking for a remote command that has a + lot of args. + + - Use rsyserr() in the various places that were still calling rprintf() + with strerror() as an arg. + + - If an rsync daemon is listening on multiple sockets (to handle both + IPv4 and IPv6 to a single port), we now close all the unneeded file + handles after we accept a connection (we used to close just one of + them). + + - Optimized the handling of larger block sizes (rsync used to slow to a + crawl if the block size got too large). + + - Optimized away a loop in hash_search(). + + - Some improvements to the sanitize_path() and clean_fname() functions + makes them more efficient and produce better results (while still + being compatible with the file-name cleaning that gets done on both + sides when sending the file-list). + + - Got rid of alloc_sanitize_path() after adding a destination-buffer + arg to sanitize_path() made it possible to put all the former's + functionality into the latter. + + - The file-list that is output when at least 4 verbose options are + specified reports the uid value on the sender even when rsync is + not running as root (since we might be sending to a root receiver). + + BUILD CHANGES: + + - Added a "gen" target to rebuild most of the generated files, + including configure, config.h.in, the man pages, and proto.h. + + - If "make proto" doesn't find some changes in the prototypes, the + proto.h file is left untouched (its time-stamp used to always be + updated). + + - The variable $STRIP (that is optionally set by the install-strip + target's rule) was changed to $INSTALL_STRIP because some systems + have $STRIP already set in the environment. + + - Fixed a build problem when SUPPORT_HARD_LINKS isn't defined. + + - When cross-compiling, the gettimeofday() function is now assumed to + be a modern version that takes two-args (since we can't test it). + + DEVELOPER RELATED: + + - The scripts in the testsuite dir were cleaned up a bit and a few + new tests added. + + - Some new diffs were added to the patches dir, and some accepted + ones were removed. + + +NEWS for rsync 2.6.2 (30 Apr 2004) +Protocol: 28 (unchanged) +Changes since 2.6.1: + + BUG FIXES: + + - Fixed a major bug in the sorting of the filenames when --relative + is used for some sources (just sources such as "/" and "/*" were + affected). This fix ensures that we ask for the right file-list + item when requesting changes from the sender. + + - Rsync now checks the return value of the close() function to + better report disk-full problems on an NFS file system. + + - Restored the old daemon-server behavior of logging error messages + rather than returning them to the user. (A better long-term fix + will be sought in the future.) + + - An obscure uninitialized-variable bug was fixed in the uid/gid + code. (This bug probably had no ill effects.) + + BUILD CHANGES: + + - Got rid of the configure check for sys/sysctl.h (it wasn't used + and was causing a problem on some systems). Also improved the + broken-largefile-locking test to try to avoid failure due to an + NFS build-dir. + + - Fixed a compile problem on systems that don't define + AI_NUMERICHOST. + + - Fixed a compile problem in the popt source for compilers that + don't support __attribute__. + + DEVELOPER RELATED: + + - Improved the testsuite's "merge" test to work on OSF1. + + - Two new diffs were added to the patches dir. + + +NEWS for rsync 2.6.1 (26 Apr 2004) +Protocol: 28 (changed) +Changes since 2.6.0: + + SECURITY FIXES: + + - Paths sent to an rsync daemon are more thoroughly sanitized when + chroot is not used. If you're running a non-read-only rsync + daemon with chroot disabled, *please upgrade*, ESPECIALLY if the + user privs you run rsync under is anything above "nobody". + + ENHANCEMENTS: + + - Lower memory use, more optimal transfer of data over the socket, + and lower CPU usage (see the INTERNAL section for details). + + - The RSYNC_PROXY environment variable can now contain a + "USER:PASS@" prefix before the "HOST:PORT" information. + (Bardur Arantsson) + + - The --progress output now mentions how far along in the transfer + we are, including both a count of files transferred and a + percentage of the total file-count that we've processed. It also + shows better current-rate-of-transfer and remaining-transfer-time + values. + + - Documentation changes now attempt to describe some often mis- + understood features more clearly. + + BUG FIXES: + + - When -x (--one-file-system) is combined with -L (--copy-links) or + --copy-unsafe-links, no symlinked files are skipped, even if the + referent file is on a different filesystem. + + - The --link-dest code now works properly for a non-root user when + (1) the UIDs of the source and destination differ and -o was + specified, or (2) when the group of the source can't be used on + the destination and -g was specified. + + - Fixed a bug in the handling of -H (hard-links) that might cause + the expanded PATH/NAME value of the current item to get + overwritten (due to an expanded-name caching bug). + + - We now reset the "new data has been sent" flag at the start of + each file we send. This makes sure that an interrupted transfer + with the --partial option set doesn't keep a shorter temp file + than the current basis file when no new data has been transferred + over the wire for that file. + + - Fixed a byte-order problem in --batch-mode on big-endian machines. + (Jay Fenlason) + + - When using --cvs-exclude, the exclude items we get from a + per-directory's .cvsignore file once again only affect that one + directory (not all following directories too). The items are also + now properly word-split and parsed without any +/- prefix parsing. + + - When specifying the USER@HOST: prefix for a file, the USER part + can now contain an '@', if needed (i.e. the last '@' is used to + find the HOST, not the first). + + - Fixed some bugs in the handling of group IDs for non-root users: + (1) It properly handles a group that the sender didn't have a name + for (it would previously skip changing the group on any files in + that group). (2) If --numeric-ids is used, rsync no longer + attempts to set groups that the user doesn't have the permission + to set. + + - Fixed the "refuse options" setting in the rsyncd.conf file. + + - Improved the -x (--one-file-system) flag's handling of any mount- + point directories we encounter. It is both more optimal (in that + it no longer does a useless scan of the contents of the mount- + point dirs) and also fixes a bug where a remapped mount of the + original filesystem could get discovered in a subdir we should be + ignoring. + + - Rsync no longer discards a double-slash at the start of a filename + when trying to open the file. It also no longer constructs names + that start with a double slash (unless the user supplied them). + + - Path-specifying options to a daemon should now work the same with + or without chroot turned on. Previously, such a option (such as + --link-dest) would get its absolute path munged into a relative + one if chroot was not on, making that setting fairly useless. + Rsync now transforms the path into one that is based on the + module's base dir when chroot is not enabled. + + - Fixed a compatibility problem interacting with older rsync + versions that might send us an empty --suffix value without + telling us that --backup-dir was specified. + + - The "hosts allow" option for a daemon-over-remote-shell process + now has improved support for IPv6 addresses and a fix for systems + that have a length field in their socket structs. + + - Fixed the ability to request an empty backup --suffix when sending + files to an rsync daemon. + + - Fixed an option-parsing bug when --files-from was sent to a server + sender. + + INTERNAL: + + - Most of the I/O is now buffered, which results in a pretty large + speedup when running under MS Windows. (Craig Barratt) + + - Optimizations to the name-handling/comparing code have made some + significant reductions in user-CPU time for large file sets. + + - Some cleanup of the variable types make the code more consistent. + + - Reduced memory requirements of hard link preservation. + (J.W. Schultz) + + - Implemented a new algorithm for hard-link handling that speeds up + the code significantly. (J.W. Schultz and Wayne Davison) + + - The --hard-link option now uses the first existing file in the + group of linked files as the basis for the transfer. This + prevents the sub-optimal transfer of a file's data when a new + hardlink is added on the sending side and it sorts alphabetically + earlier in the list than the files that are already present on the + receiving side. + + - Dropped support for protocol versions less than 20 (2.3.0 released + 15 Mar 1999) and activated warnings for protocols less than 25 + (2.5.0 released 23 Aug 2001). (Wayne Davison and J.W. Schultz, + severally) + + - More optimal data transmission for --hard-links (protocol 28). + + - More optimal data transmission for --checksum (protocol 28). + + - Less memory is used when --checksum is specified. + + - Less memory is used in the file list (a per-file savings). + + - The generator is now better about not modifying the file list + during the transfer in order to avoid a copy-on-write memory + bifurcation (on systems where fork() uses shared memory). + Previously, rsync's shared memory would slowly become unshared, + resulting in real memory usage nearly doubling on the receiving + side by the end of the transfer. Now, as long as permissions + are being preserved, the shared memory should remain that way + for the entire transfer. + + - Changed hardlink info and file_struct + strings to use allocation + pools. This reduces memory use for large file-sets and permits + freeing memory to the OS. (J.W. Schultz) + + - The 2 pipes used between the receiver and generator processes + (which are forked on the same machine) were reduced to 1 pipe and + the protocol improved so that (1) it is now impossible to have the + "redo" pipe fill up and hang rsync, and (2) trailing messages from + the receiver don't get lost on their way through the generator + over to the sender (which mainly affected hard-link messages and + verbose --stats output). + + - Improved the internal uid/gid code to be more portable and a + little more optimized. + + - The device numbers sent when using --devices are now sent as + separate major/minor values with 32-bit accuracy (protocol 28). + Previously, the copied devices were sent as a single 32-bit + number. This will make inter-operation of 64-bit binaries more + compatible with their 32-bit brethren (with both ends of the + connection are using protocol 28). Note that optimizations in the + binary protocol for sending the device numbers often results in + fewer bytes being used than before, even though more precision is + now available. + + - Some cleanup of the exclude/include structures and its code made + things clearer (internally), simpler, and more efficient. + + - The reading & writing of the file-list in batch-mode is now + handled by the same code that sends & receives the list over the + wire. This makes it much easier to maintain. (Note that the + batch code is still considered to be experimental.) + + BUILD CHANGES: + + - The configure script now accepts --with-rsyncd-conf=PATH to + override the default value of the /etc/rsyncd.conf file. + + - Fixed configure bug when running "./configure --disable-ipv6". + + - Fixed compilation problem on Tru64 Unix (having to do with + sockaddr.sa_len and sockaddr.sin_len). + + DEVELOPER RELATED: + + - Fixed "make test" bug when build dir is not the source dir. + + - Added a couple extra diffs in the "patches" dir, removed the ones + that got applied, and rebuilt the rest. + + +NEWS for rsync 2.6.0 (1 Jan 2004) +Protocol: 27 (changed) +Changes since 2.5.7: + + ENHANCEMENTS: + + * "ssh" is now the default remote shell for rsync. If you want to + change this, configure like this: "./configure --with-rsh=rsh". + + * Added --files-from, --no-relative, --no-implied-dirs, and --from0. + Note that --from0 affects the line-ending character for all the + files read by the --*-from options. (Wayne Davison) + + * Length of csum2 is now per-file starting with protocol version + 27. (J.W. Schultz) + + * Per-file dynamic block size is now sqrt(file length). The + per-file checksum size is determined according to an algorithm + provided by Donovan Baarda which reduces the probability of rsync + algorithm corrupting data and falling back using the whole md4 + checksums. (J.W. Schultz, Donovan Baarda) + + * The --stats option no longer includes the (debug) malloc summary + unless the verbose option was specified at least twice. + + * Added a new error/warning code for when files vanish from the + sending side. Made vanished source files not interfere with the + file-deletion pass when --delete-after was specified. + + * Various trailing-info sections are now preceded by a newline. + + BUG FIXES: + + * Fixed several exclude/include matching bugs when using wild-cards. + This has a several user-visible effects, all of which make the + matching more consistent and intuitive. This should hopefully not + cause anyone problems since it makes the matching work more like + what people are expecting. (Wayne Davison) + + - A pattern with a "**" no longer causes a "*" to match slashes. + For example, with "/*/foo/**", "foo" must be 2 levels deep. + [If your string has BOTH "*" and "**" wildcards, changing the + "*" wildcards to "**" will provide the old behavior in all + versions.] + + - "**/foo" now matches at the base of the transfer (like /foo + does). [Use "/**/foo" to get the old behavior in all versions.] + + - A non-anchored wildcard term floats to match beyond the base of + the transfer. E.g. "CVS/R*" matches at the end of the path, + just like the non-wildcard term "CVS/Root" does. [Use "/CVS/R*" + to get the old behavior in all versions.] + + - Including a "**" in the match term causes it to be matched + against the entire path, not just the name portion, even if + there aren't any interior slashes in the term. E.g. "foo**bar" + would exclude "/path/foo-bar" (just like before) as well as + "/foo-path/baz-bar" (unlike before). [Use "foo*bar" to get the + old behavior in all versions.] + + * The exclude list specified in the daemon's config file is now + properly applied to the pulled items no matter how deep the + user's file-args are in the source tree. (Wayne Davison) + + * For protocol version >= 27, mdfour_tail() is called when the + block size (including checksum_seed) is a multiple of 64. + Previously it was not called, giving the wrong MD4 checksum. + (Craig Barratt) + + * For protocol version >= 27, a 64 bit bit counter is used in + mdfour.c as required by the RFC. Previously only a 32 bit bit + counter was used, causing incorrect MD4 file checksums for + file sizes >= 512MB - 4. (Craig Barratt) + + * Fixed a crash bug when interacting with older rsync versions and + multiple files of the same name are destined for the same dir. + (Wayne Davison) + + * Keep tmp names from overflowing MAXPATHLEN. + + * Make --link-dest honor the absence of -p, -o, and -g. + + * Made rsync treat a trailing slash in the destination in a more + consistent manner. + + * Fixed file I/O error detection. (John Van Essen) + + * Fixed bogus "malformed address {hostname}" message in rsyncd log + when checking IP address against hostnames from "hosts allow" + and "hosts deny" parameters in config file. + + * Print heap statistics when verbose >= 2 instead of when >= 1. + + * Fixed a compression (-z) bug when syncing a mostly-matching file + that contains already-compressed data. (Yasuoka Masahiko and + Wayne Davison) + + * Fixed a bug in the --backup code that could cause deleted files + to not get backed up. + + * When the backup code makes new directories, create them with mode + 0700 instead of 0755 (since the directory permissions in the + backup tree are not yet copied from the main tree). + + * Call setgroups() in a more portable manner. + + * Improved file-related error messages to better indicate exactly + what pathname failed. (Wayne Davison) + + * Fixed some bugs in the handling of --delete and --exclude when + using the --relative (-R) option. (Wayne Davison) + + * Fixed bug that prevented regular files from replacing + special files and caused a directory in --link-dest or + --compare-dest to block the creation of a file with the + same path. A directory still cannot be replaced by a + regular file unless --delete specified. (J.W. Schultz) + + * Detect and report when open or opendir succeed but read and + readdir fail caused by network filesystem issues and truncated + files. (David Norwood, Michael Brown, J.W. Schultz) + + * Added a fix that should give ssh time to restore the tty settings + if the user presses Ctrl-C at an ssh password prompt. + + INTERNAL: + + * Eliminated vestigial support for old versions that we stopped + supporting. (J.W. Schultz) + + * Simplified some of the option-parsing code. (Wayne Davison) + + * Some cleanup made to the exclude code, as well as some new + defines added to enhance readability. (Wayne Davison) + + * Changed the protocol-version code so that it can interact at a + lower protocol level than the maximum supported by both sides. + Added an undocumented option, --protocol=N, to force the value + we advertise to the other side (primarily for testing purposes). + (Wayne Davison) + + +NEWS for rsync 2.5.7 (4 Dec 2003) +Protocol: 26 (unchanged) +Changes since 2.5.6: + + SECURITY FIXES: + + * Fix buffer handling bugs. (Andrew Tridgell, Martin Pool, Paul + Russell, Andrea Barisani) + + +NEWS for rsync 2.5.6, aka "the dwd-between-jobs release" (26 Jan 2003) +Protocol: 26 (unchanged) +Changes since 2.5.5: + + ENHANCEMENTS: + + * The --delete-after option now implies --delete. (Wayne Davison) + + * The --suffix option can now be used with --backup-dir. (Michael + Zimmerman) + + * Combining "::" syntax with the --rsh/-e option now uses the + specified remote-shell as a transport to talk to a (newly-spawned) + server-daemon. This allows someone to use daemon features, such + as modules, over a secure protocol, such as ssh. (JD Paul) + + * The rsync:// syntax for daemon connections is now accepted in the + destination field. + + * If the file name given to --include-from or --exclude-from is "-", + rsync will read from standard input. (J.W. Schultz) + + * New option --link-dest which is like --compare-dest except that + unchanged files are hard-linked in to the destination directory. + (J.W. Schultz) + + * Don't report an error if an excluded file disappears during an + rsync run. (Eugene Chupriyanov and Bo Kersey) + + * Added .svn to --cvs-exclude list to support subversion. (Jon + Middleton) + + * Properly support IPv6 addresses in the rsyncd.conf "hosts allow" + and "hosts deny" fields. (Hideaki Yoshifuji) + + * Changed exclude file handling to permit DOS or MAC style line + terminations. (J.W. Schultz) + + * Ignore errors from chmod when -p/-a/--preserve-perms is not set. + (Dave Dykstra) + + BUG FIXES: + + * Fix "forward name lookup failed" errors on AIX 4.3.3. (John + L. Allen, Martin Pool) + + * Generate each file's rolling-checksum data as we send it, not + in a separate (memory-eating) pass before hand. This prevents + timeout errors on really large files. (Stefan Nehlsen) + + * Fix compilation on Tru64. (Albert Chin, Zoong Pham) + + * Better handling of some client-server errors. (Martin Pool) + + * Fixed a crash that would occur when sending a list of files that + contains a duplicate name (if it sorts to the end of the file + list) and using --delete. (Wayne Davison) + + * Fixed the file-name duplicate-removal code when dealing with multiple + dups in a row. (Wayne Davison) + + * Fixed a bug that caused rsync to lose the exit status of its child + processes and sometimes return an exit code of 0 instead of showing + an error. (David R. Staples, Dave Dykstra) + + * Fixed bug in --copy-unsafe-links that caused it to be completely + broken. (Dave Dykstra) + + * Prevent infinite recursion in cleanup code under certain circumstances. + (Sviatoslav Sviridov and Marc Espie) + + * Fixed a bug that prevented rsync from creating intervening directories + when --relative-paths/-R is set. (Craig Barratt) + + * Prevent "Connection reset by peer" messages from Cygwin. (Randy O'Meara) + + INTERNAL: + + * Many code cleanups and improved internal documentation. (Martin + Pool, Nelson Beebe) + + * Portability fixes. (Dave Dykstra and Wayne Davison) + + * More test cases. (Martin Pool) + + * Some test-case fixes. (Brian Poole, Wayne Davison) + + * Updated included popt to the latest vendor drop, version 1.6.4. + (Jos Backus) + + * Updated config.guess and config.sub to latest versions; this + means rsync should build on more platforms. (Paul Green) + + +NEWS for rsync 2.5.5, aka Snowy River (2 Apr 2002) +Protocol: 26 (unchanged) +Changes since 2.5.4: + + ENHANCEMENTS: + + * With --progress, when a transfer is complete show the time taken; + otherwise show expected time to complete. (Cameron Simpson) + + * Make "make install-strip" works properly, and "make install" + accepts a DESTDIR variable for help in building binary packages. + (Peter Breitenlohner, Greg Louis) + + * If configured with --enable-maintainer-mode, then on receipt of + a fatal signal rsync will try to open an xterm running gdb, + similarly to Samba's "panic action" or GNOME's bug-buddy. + (Martin Pool) + + + BUG FIXES: + + * Fix situation where failure to fork (e.g. because out of process + slots) would cause rsync to kill all processes owned by the + current user. Yes, really! (Paul Haas, Martin Pool) + + * Fix test suite on Solaris. (Jos Backus, Martin Pool) + + * Fix minor memory leak in socket code. (Dave Dykstra, Martin + Pool.) + + * Fix --whole-file problem that caused it to be the default even + for remote connections. (Martin Pool, Frank Schulz) + + * Work around bug in Mac OS X mkdir(2), which cannot handle + trailing slashes. + + (Martin Pool) + + * Improved network error handling. (Greg A. Woods) + + +NEWS for rsync 2.5.4, aka "Imitation lizard skin" (13 Mar 2002) +Protocol: 26 (unchanged) +Changes since 2.5.3: + + BUG FIXES: + + * Additional fix for zlib double-free bug. (Martin Pool, Andrew + Tridgell) (CVE CAN-2002-0059) + + ENHANCEMENTS: + + * Merge in changes from zlib 1.1.3 to zlib 1.1.4. (Jos Backus) + (Note that rsync still uses a custom version of zlib; you can + not just link against a system library. See zlib/README.rsync) + + * Additional test cases for --compress. (Martin Pool) + + +NEWS for rsync 2.5.3, aka "Happy 26" (11 Mar 2002) +Protocol: 26 (unchanged) +Changes since 2.5.2: + + SECURITY FIXES: + + * Make sure that supplementary groups are removed from a server + process after changing uid and gid. (Ethan Benson) (Debian bug + #132272, CVE CAN-2002-0080) + + BUG FIXES: + + * Fix zlib double-free bug. (Owen Taylor, Mark J Cox) (CVE + CAN-2002-0059) + + * Fixed problem that in many cases caused the error message + unexpected read size of 0 in map_ptr + and resulted in the wrong data being copied. + + * Fixed compilation errors on some systems caused by the use of + "unsigned int64" in rsync.h. + + * Fixed problem on systems such as Sunos4 that do not support realloc + on a NULL pointer; error was "out of memory in flist_expand". + + * Fix for rsync server processes hanging around after the client + unexpectedly disconnects. (Colin Walters) (Debian bug #128632) + + * Cope with BSD systems on which mkdir() will not accept a trailing + slash. + + ENHANCEMENTS: + + * Merge in changes from zlib 1.1.2 to zlib 1.1.3. (Note that + rsync still uses a custom version of zlib; you can not just link + against a system library. See zlib/README.rsync) + + * Command to initiate connections is only shown with -vv, rather + than -v as in 2.5.2. Output from plain -v is more similar to + what was historically used so as not to break scripts that try + to parse the output. + + * Added --no-whole-file and --no-blocking-io options (Dave Dykstra) + + * Made the --write-batch and --read-batch options actually work + and added documentation in the man page (Jos Backus) + + * If the daemon is unable to fork a child to accept a connection, + print an error message. (Colin Walters) + + +NEWS for rsync 2.5.2 (26 Jan 2002) +Protocol: 26 (changed) +Changes since 2.5.1: + + SECURITY FIXES: + + * Signedness security patch from Sebastian Krahmer + -- in some cases we were not sufficiently + careful about reading integers from the network. + + BUG FIXES: + + * Fix possible string mangling in log files. + + * Fix for setting local address of outgoing sockets. + + * Better handling of hardlinks and devices on platforms with + 64-bit dev_t or ino_t. + + * Name resolution on machines supporting IPv6 is improved. + + * Fix for device nodes. (dann frazier) (Debian #129135) + + ENHANCEMENTS: + + * With -v, rsync now shows the command used to initiate an ssh/rsh + connection. + + * --statistics now shows memory heap usage on platforms that + support mallinfo(). + + * "The Ted T'so school of program optimization": make progress + visible and people will think it's faster. (With --progress, + rsync will show you how many files it has seen as it builds the + file_list, giving some indication that it has not hung.) + + * Improvements to batch mode support. This is still experimental + but testing would be welcome. (Jos Backus) + + * New --ignore-existing option, patch previously distributed with + Vipul's Razor. (Debian #124286) + + +NEWS for rsync 2.5.1 (3 Jan 2002) +Protocol: 25 (unchanged) +Changes since 2.5.0: + + BUG FIXES: + + * Fix for segfault in --daemon mode configuration parser. (Paul + Mackerras) + + * Correct string<->address parsing for both IPv4 and 6. + (YOSHIFUJI Hideaki, SUMIKAWA Munechika and Jun-ichiro "itojun" + Hagino) + + * Various fixes for IPv6 support. (Dave Dykstra) + + * rsync.1 typo fix. (Matt Kraai) + + * Test suite typo fixes. (Tom Schmidt) + + * rsync.1 grammar and clarity improvements. (Edward + Welbourne) + + * Correction to ./configure tests for inet_ntop. (Jeff Garzik) + + ENHANCEMENTS: + + * --progress and -P now show estimated data transfer rate (in a + multiple of bytes/s) and estimated time to completion. (Rik + Faith) + + * --no-detach option, required to run as a W32 service and also + useful when running on Unix under daemontools, AIX's SRC, or a + debugger. (Max Bowsher, Jos Backus) + + * Clearer error messages for some conditions. + + +NEWS for rsync 2.5.0 (30 Nov 2001) +Protocol: 25 (changed) +Changes since 2.4.6: + + ANNOUNCEMENTS + + * Martin Pool is now a co-maintainer. + + NEW FEATURES + + * Support for LSB-compliant packaging + + * Shell wildcards are allowed in "auth users" lines. + + * Merged UNC rsync+ patch to support creation of standalone patch + sets. By Bert J. Dempsey and Debra Weiss, updated by Jos + Backus. + + * IPv6 support based on a patch from KAME.net, on systems + including modern versions of Linux, Solaris, and HP-UX. Also + includes IPv6 compatibility functions for old OSs by the + Internet Software Consortium, Paul Vixie, the OpenSSH + portability project, and OpenBSD. + + ENHANCEMENTS + + * Include/exclude cluestick: with -vv, print out whether files are + included or excluded and why. + + * Many error messages have more friendly explanations and more + details. + + * Manual page improvements plus scanty protocol documentation. + + * When running as --daemon in the background and using a "log + file" rsyncd.conf directive, close the log file every time it is + open when going to sleep on the socket. This allows the log + file to get cleaned out by another process. + + * Change to using libpopt rather than getopt for processing + options. This makes the code cleaner and the behaviour more + consistent across platforms. popt is included and built if not + installed on the platform. + + * More details in --version, including note about whether 64-bit + files, symlinks and hardlinks are supported. + + * MD4 code may use less CPU cycles. + + * Use mkstemp on systems where it is secure. If we use mktemp, + explain that we do it in a secure way. + + * --whole-file is the default when source and target are on the + local machine. + + BUG FIXES: + + * Fix for various bugs causing rsync to hang. + + * Attempt to fix Large File Summit support on AIX. + + * Attempt to fix error handling lockup bug. + + * Give a non-0 exit code if *any* of the files we have been asked + to transfer fail to transfer. + + * For log messages containing ridiculously long strings that might + overflow a buffer rsync no longer aborts, but rather prints an + ellipsis at the end of the string. (Patch from Ed Santiago.) + + PLATFORMS: + + * Improved support for UNICOS (tested on Cray T3E and Cray SV1) + + * autoconf2.52 (or later) is now required to rebuild the autoconf + scripts. It is not required to simply build rsync. + + * Platforms thought to work in this release: + + Cray SV1 UNICOS 10.0.0.8 cc + Debian Linux 2.2 UltraSparc gcc + Debian Linux testing/unstable ARM gcc + FreeBSD 3.3-RELEASE i386 cc + FreeBSD 4.1.1-RELEASE i386 cc + FreeBSD 4.3-STABLE i386 cc + HP PA-RISC HP-UX 10.20 gcc + HP PA-RISC HP-UX 11.11 cc + IRIX 6.5 MIPS cc + IRIX 6.5 MIPS gcc + Mac OS X PPC (--disable-ipv6) cc + NetBSD 1.5 i386 gcc + NetBSD Current i386 cc + OpenBSD 2.5 Sparc gcc + OpenBSD 2.9 i386 cc + OpenBSD Current i386 cc + RedHat 6.2 i386 gcc + RedHat 6.2 i386 insure++ + RedHat 7.0 i386 gcc + RedHat 7.1 i386 (Kernel 2.4.10) gcc + Slackware 8.0 i686 (Kernel 2.4.10) + Solaris 8 UltraSparc cc + Solaris 8 UltraSparc gcc + Solaris 8 i386 gcc + SuSE 7.1 i386 gcc2.95.2 + SuSE 7.1 ppc gcc2.95.2 + i386-pc-sco3.2v5.0.5 cc + i386-pc-sco3.2v5.0.5 gcc + powerpc-ibm-aix4.3.3.0 cc + i686-unknown-sysv5UnixWare7.1.0 gcc + i686-unknown-sysv5UnixWare7.1.0 cc + + TESTING: + + * The existing test.sh script by Phil Hands has been merged into a + test framework that works from both "make check" and the Samba + build farm. + +Partial Protocol History + RELEASE DATE VER. DATE OF COMMIT* PROTOCOL + 22 Jun 2014 3.1.1 31 + 28 Sep 2013 3.1.0 31 Aug 2008 31 + 23 Sep 2011 3.0.9 30 + 26 Mar 2011 3.0.8 30 + 31 Dec 2009 3.0.7 30 + 08 May 2009 3.0.6 30 + 28 Dec 2008 3.0.5 30 + 06 Sep 2008 3.0.4 30 + 29 Jun 2008 3.0.3 30 + 08 Apr 2008 3.0.2 30 + 03 Apr 2008 3.0.1 30 + 01 Mar 2008 3.0.0 11 Nov 2006 30 + 06 Nov 2006 2.6.9 29 + 22 Apr 2006 2.6.8 29 + 11 Mar 2006 2.6.7 29 + 28 Jul 2005 2.6.6 29 + 01 Jun 2005 2.6.5 29 + 30 Mar 2005 2.6.4 17 Jan 2005 29 + 30 Sep 2004 2.6.3 28 + 30 Apr 2004 2.6.2 28 + 26 Apr 2004 2.6.1 08 Jan 2004 28 + 01 Jan 2004 2.6.0 10 Apr 2003 27 (MAX=40) + 04 Dec 2003 2.5.7 26 + 26 Jan 2003 2.5.6 26 + 02 Apr 2002 2.5.5 26 + 13 Mar 2002 2.5.4 26 + 11 Mar 2002 2.5.3 26 + 26 Jan 2002 2.5.2 11 Jan 2002 26 + 03 Jan 2002 2.5.1 25 + 30 Nov 2001 2.5.0 23 Aug 2001 25 + 06 Sep 2000 2.4.6 24 + 19 Aug 2000 2.4.5 24 + 29 Jul 2000 2.4.4 24 + 09 Apr 2000 2.4.3 24 + 30 Mar 2000 2.4.2 24 + 30 Jan 2000 2.4.1 29 Jan 2000 24 + 29 Jan 2000 2.4.0 28 Jan 2000 23 + 25 Jan 2000 2.3.3 23 Jan 2000 22 + 08 Nov 1999 2.3.2 26 Jun 1999 21 + 06 Apr 1999 2.3.1 20 + 15 Mar 1999 2.3.0 15 Mar 1999 20 + 25 Nov 1998 2.2.1 19 + 03 Nov 1998 2.2.0 19 + 09 Sep 1998 2.1.1 19 + 20 Jul 1998 2.1.0 19 + 17 Jul 1998 2.0.19 19 + 18 Jun 1998 2.0.17 19 + 01 Jun 1998 2.0.16 19 + 27 May 1998 2.0.13 27 May 1998 19 + 26 May 1998 2.0.12 18 + 22 May 1998 2.0.11 18 + 18 May 1998 2.0.9 18 May 1998 18 + 17 May 1998 2.0.8 17 + 15 May 1998 2.0.1 17 + 14 May 1998 2.0.0 17 + 17 Apr 1998 1.7.4 17 + 13 Apr 1998 1.7.3 17 + 05 Apr 1998 1.7.2 17 + 26 Mar 1998 1.7.1 17 + 26 Mar 1998 1.7.0 26 Mar 1998 17 (MAX=30) + 13 Jan 1998 1.6.9 13 Jan 1998 15 (MAX=20) + +* DATE OF COMMIT is the date the protocol change was committed to CVS. diff --git a/rsync/README b/rsync/README new file mode 100644 index 0000000..9007a25 --- /dev/null +++ b/rsync/README @@ -0,0 +1,140 @@ +WHAT IS RSYNC? +-------------- + +Rsync is a fast and extraordinarily versatile file copying tool for +both remote and local files. + +Rsync uses a delta-transfer algorithm which provides a very fast method +for bringing remote files into sync. It does this by sending just the +differences in the files across the link, without requiring that both +sets of files are present at one of the ends of the link beforehand. At +first glance this may seem impossible because the calculation of diffs +between two files normally requires local access to both files. + +A technical report describing the rsync algorithm is included with this +package. + + +USAGE +----- + +Basically you use rsync just like scp, but rsync has many additional +options. To get a complete list of supported options type: + + rsync --help + +See the manpage for more detailed information. + + +SETUP +----- + +Rsync normally uses ssh or rsh for communication with remote systems. +It does not need to be setuid and requires no special privileges for +installation. You must, however, have a working ssh or rsh system. +Using ssh is recommended for its security features. + +Alternatively, rsync can run in `daemon' mode, listening on a socket. +This is generally used for public file distribution, although +authentication and access control are available. + +To install rsync, first run the "configure" script. This will create a +Makefile and config.h appropriate for your system. Then type "make". + +Note that on some systems you will have to force configure not to use +gcc because gcc may not support some features (such as 64 bit file +offsets) that your system may support. Set the environment variable CC +to the name of your native compiler before running configure in this +case. + +Once built put a copy of rsync in your search path on the local and +remote systems (or use "make install"). That's it! + + +RSYNC DAEMONS +------------- + +Rsync can also talk to "rsync daemons" which can provide anonymous or +authenticated rsync. See the rsyncd.conf(5) man page for details on how +to setup an rsync daemon. See the rsync(1) man page for info on how to +connect to an rsync daemon. + + +WEB SITE +-------- + +The main rsync web site is here: + + http://rsync.samba.org/ + +You'll find a FAQ list, downloads, resources, HTML versions of the +manpages, etc. + + +MAILING LISTS +------------- + +There is a mailing list for the discussion of rsync and its applications +that is open to anyone to join. New releases are announced on this +list, and there is also an announcement-only mailing list for those that +want official announcements. See the mailing-list page for full +details: + + http://rsync.samba.org/lists.html + + +BUG REPORTS +----------- + +To visit this web page for full the details on bug reporting: + + http://rsync.samba.org/bugzilla.html + +That page contains links to the current bug list, and information on how +to report a bug well. You might also like to try searching the Internet +for the error message you've received, or looking in the mailing list +archives at: + + http://mail-archive.com/rsync@lists.samba.org/ + +To send a bug report, follow the instructions on the bug-tracking +page of the web site. + +Alternately, email your bug report to rsync@lists.samba.org . + + +GIT REPOSITORY +-------------- + +If you want to get the very latest version of rsync direct from the +source code repository then you can use git: + + git clone git://git.samba.org/rsync.git + +See the download page for full details on all the ways to grab the +source, including nightly tar files, web-browsing of the git repository, +etc.: + + http://rsync.samba.org/download.html + + +COPYRIGHT +--------- + +Rsync was originally written by Andrew Tridgell and is currently +maintained by Wayne Davison. It has been improved by many developers +from around the world. + +Rsync may be used, modified and redistributed only under the terms of +the GNU General Public License, found in the file COPYING in this +distribution, or at: + + http://www.fsf.org/licenses/gpl.html + + +AVAILABILITY +------------ + +The main web site for rsync is http://rsync.samba.org/ +The main ftp site is ftp://rsync.samba.org/pub/rsync/ +This is also available as rsync://rsync.samba.org/rsyncftp/ diff --git a/rsync/TODO b/rsync/TODO new file mode 100644 index 0000000..9baf463 --- /dev/null +++ b/rsync/TODO @@ -0,0 +1,528 @@ +-*- indented-text -*- + +FEATURES ------------------------------------------------------------ +Use chroot only if supported +Allow supplementary groups in rsyncd.conf 2002/04/09 +Handling IPv6 on old machines +Other IPv6 stuff +Add ACL support 2001/12/02 +proxy authentication 2002/01/23 +SOCKS 2002/01/23 +FAT support +--diff david.e.sewell 2002/03/15 +Add daemon --no-fork option +Create more granular verbosity 2003/05/15 + +DOCUMENTATION -------------------------------------------------------- +Keep list of open issues and todos on the web site +Perhaps redo manual as SGML + +LOGGING -------------------------------------------------------------- +Memory accounting +Improve error messages +Better statistics Rasmus 2002/03/08 +Perhaps flush stdout like syslog +Log child death on signal +verbose output David Stein 2001/12/20 +internationalization + +DEVELOPMENT -------------------------------------------------------- +Handling duplicate names +Use generic zlib 2002/02/25 +TDB 2002/03/12 +Splint 2002/03/12 + +PERFORMANCE ---------------------------------------------------------- +Traverse just one directory at a time +Allow skipping MD4 file_sum 2002/04/08 +Accelerate MD4 + +TESTING -------------------------------------------------------------- +Torture test +Cross-test versions 2001/08/22 +Test on kernel source +Test large files +Create mutator program for testing +Create configure option to enable dangerous tests +Create pipe program for testing +Create test makefile target for some tests + +RELATED PROJECTS ----------------------------------------------------- +rsyncsh +http://rsync.samba.org/rsync-and-debian/ +rsyncable gzip patch +rsyncsplit as alternative to real integration with gzip? +reverse rsync over HTTP Range + + + +FEATURES ------------------------------------------------------------ + + +Use chroot only if supported + + If the platform doesn't support it, then don't even try. + + If running as non-root, then don't fail, just give a warning. + (There was a thread about this a while ago?) + + http://lists.samba.org/pipermail/rsync/2001-August/thread.html + http://lists.samba.org/pipermail/rsync/2001-September/thread.html + + -- -- + + +Allow supplementary groups in rsyncd.conf 2002/04/09 + + Perhaps allow supplementary groups to be specified in rsyncd.conf; + then make the first one the primary gid and all the rest be + supplementary gids. + + -- -- + + +Handling IPv6 on old machines + + The KAME IPv6 patch is nice in theory but has proved a bit of a + nightmare in practice. The basic idea of their patch is that rsync + is rewritten to use the new getaddrinfo()/getnameinfo() interface, + rather than gethostbyname()/gethostbyaddr() as in rsync 2.4.6. + Systems that don't have the new interface are handled by providing + our own implementation in lib/, which is selectively linked in. + + The problem with this is that it is really hard to get right on + platforms that have a half-working implementation, so redefining + these functions clashes with system headers, and leaving them out + breaks. This affects at least OSF/1, RedHat 5, and Cobalt, which + are moderately improtant. + + Perhaps the simplest solution would be to have two different files + implementing the same interface, and choose either the new or the + old API. This is probably necessary for systems that e.g. have + IPv6, but gethostbyaddr() can't handle it. The Linux manpage claims + this is currently the case. + + In fact, our internal sockets interface (things like + open_socket_out(), etc) is much narrower than the getaddrinfo() + interface, and so probably simpler to get right. In addition, the + old code is known to work well on old machines. + + We could drop the rather large lib/getaddrinfo files. + + -- -- + + +Other IPv6 stuff + + Implement suggestions from http://www.kame.net/newsletter/19980604/ + and ftp://ftp.iij.ad.jp/pub/RFC/rfc2553.txt + + If a host has multiple addresses, then listen try to connect to all + in order until we get through. (getaddrinfo may return multiple + addresses.) This is kind of implemented already. + + Possibly also when starting as a server we may need to listen on + multiple passive addresses. This might be a bit harder, because we + may need to select on all of them. Hm. + + -- -- + + +Add ACL support 2001/12/02 + + Transfer ACLs. Need to think of a standard representation. + Probably better not to even try to convert between NT and POSIX. + Possibly can share some code with Samba. + NOTE: there is a patch that implements this in the "patches" subdir. + + -- -- + + +proxy authentication 2002/01/23 + + Allow RSYNC_PROXY to be http://user:pass@proxy.foo:3128/, and do + HTTP Basic Proxy-Authentication. + + Multiple schemes are possible, up to and including the insanity that + is NTLM, but Basic probably covers most cases. + + -- -- + + +SOCKS 2002/01/23 + + Add --with-socks, and then perhaps a command-line option to put them + on or off. This might be more reliable than LD_PRELOAD hacks. + + -- -- + + +FAT support + + rsync to a FAT partition on a Unix machine doesn't work very well at + the moment. I think we get errors about invalid filenames and + perhaps also trying to do atomic renames. + + I guess the code to do this is currently #ifdef'd on Windows; + perhaps we ought to intelligently fall back to it on Unix too. + + -- -- + + +--diff david.e.sewell 2002/03/15 + + Allow people to specify the diff command. (Might want to use wdiff, + gnudiff, etc.) + + Just diff the temporary file with the destination file, and delete + the tmp file rather than moving it into place. + + Interaction with --partial. + + Security interactions with daemon mode? + + -- -- + + +Add daemon --no-fork option + + Very useful for debugging. Also good when running under a + daemon-monitoring process that tries to restart the service when the + parent exits. + + -- -- + + +Create more granular verbosity 2003/05/15 + + Control output with the --report option. + + The option takes as a single argument (no whitespace) a + comma delimited lists of keywords. + + This would separate debugging from "logging" as well as + fine grained selection of statistical reporting and what + actions are logged. + + http://lists.samba.org/archive/rsync/2003-May/006059.html + + -- -- + +DOCUMENTATION -------------------------------------------------------- + + +Keep list of open issues and todos on the web site + + -- -- + + +Perhaps redo manual as SGML + + The man page is getting rather large, and there is more information + that ought to be added. + + TexInfo source is probably a dying format. + + Linuxdoc looks like the most likely contender. I know DocBook is + favoured by some people, but it's so bloody verbose, even with emacs + support. + + -- -- + +LOGGING -------------------------------------------------------------- + + +Memory accounting + + At exit, show how much memory was used for the file list, etc. + + Also we do a wierd exponential-growth allocation in flist.c. I'm + not sure this makes sense with modern mallocs. At any rate it will + make us allocate a huge amount of memory for large file lists. + + -- -- + + +Improve error messages + + If we hang or get SIGINT, then explain where we were up to. Perhaps + have a static buffer that contains the current function name, or + some kind of description of what we were trying to do. This is a + little easier on people than needing to run strace/truss. + + "The dungeon collapses! You are killed." Rather than "unexpected + eof" give a message that is more detailed if possible and also more + helpful. + + If we get an error writing to a socket, then we should perhaps + continue trying to read to see if an error message comes across + explaining why the socket is closed. I'm not sure if this would + work, but it would certainly make our messages more helpful. + + What happens if a directory is missing -x attributes. Do we lose + our load? (Debian #28416) Probably fixed now, but a test case would + be good. + + -- -- + + +Better statistics Rasmus 2002/03/08 + + + hey, how about an rsync option that just gives you the + summary without the list of files? And perhaps gives + more information like the number of new files, number + of changed, deleted, etc. ? + + + nice idea there is --stats but at the moment it's very + tridge-oriented rather than user-friendly it would be + nice to improve it that would also work well with + --dryrun + + -- -- + + +Perhaps flush stdout like syslog + + Perhaps flush stdout after each filename, so that people trying to + monitor progress in a log file can do so more easily. See + http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=48108 + + -- -- + + +Log child death on signal + + If a child of the rsync daemon dies with a signal, we should notice + that when we reap it and log a message. + + -- -- + + +verbose output David Stein 2001/12/20 + + At end of transfer, show how many files were or were not transferred + correctly. + + -- -- + + +internationalization + + Change to using gettext(). Probably need to ship this for platforms + that don't have it. + + Solicit translations. + + Does anyone care? Before we bother modifying the code, we ought to + get the manual translated first, because that's possibly more useful + and at any rate demonstrates desire. + + -- -- + +DEVELOPMENT -------------------------------------------------------- + +Handling duplicate names + + Some folks would like rsync to be deterministic in how it handles + duplicate names that come from mering multiple source directories + into a single destination directory; e.g. the last name wins. We + could do this by switching our sort algorithm to one that will + guarantee that the names won't be reordered. Alternately, we could + assign an ever-increasing number to each item as we insert it into + the list and then make sure that we leave the largest number when + cleaning the file list (see clean_flist()). Another solution would + be to add a hash table, and thus never put any duplicate names into + the file list (and bump the protocol to handle this). + + -- -- + + +Use generic zlib 2002/02/25 + + Perhaps don't use our own zlib. + + Advantages: + + - will automatically be up to date with bugfixes in zlib + + - can leave it out for small rsync on e.g. recovery disks + + - can use a shared library + + - avoids people breaking rsync by trying to do this themselves and + messing up + + Should we ship zlib for systems that don't have it, or require + people to install it separately? + + Apparently this will make us incompatible with versions of rsync + that use the patched version of rsync. Probably the simplest way to + do this is to just disable gzip (with a warning) when talking to old + versions. + + -- -- + + +Splint 2002/03/12 + + Build rsync with SPLINT to try to find security holes. Add + annotations as necessary. Keep track of the number of warnings + found initially, and see how many of them are real bugs, or real + security bugs. Knowing the percentage of likely hits would be + really interesting for other projects. + + -- -- + +PERFORMANCE ---------------------------------------------------------- + +Allow skipping MD4 file_sum 2002/04/08 + + If we're doing a local transfer, or using -W, then perhaps don't + send the file checksum. If we're doing a local transfer, then + calculating MD4 checksums uses 90% of CPU and is unlikely to be + useful. + + We should not allow it to be disabled separately from -W, though + as it is the only thing that lets us know when the rsync algorithm + got out of sync and messed the file up (i.e. if the basis file + changed between checksum generation and reception). + + -- -- + + +Accelerate MD4 + + Perhaps borrow an assembler MD4 from someone? + + Make sure we call MD4 with properly-sized blocks whenever possible + to avoid copying into the residue region? + + -- -- + +TESTING -------------------------------------------------------------- + +Torture test + + Something that just keeps running rsync continuously over a data set + likely to generate problems. + + -- -- + + +Cross-test versions 2001/08/22 + + Part of the regression suite should be making sure that we + don't break backwards compatibility: old clients vs new + servers and so on. Ideally we would test both up and down + from the current release to all old versions. + + Run current rsync versions against significant past releases. + + We might need to omit broken old versions, or versions in which + particular functionality is broken + + It might be sufficient to test downloads from well-known public + rsync servers running different versions of rsync. This will give + some testing and also be the most common case for having different + versions and not being able to upgrade. + + The new --protocol option may help in this. + + -- -- + + +Test on kernel source + + Download all versions of kernel; unpack, sync between them. Also + sync between uncompressed tarballs. Compare directories after + transfer. + + Use local mode; ssh; daemon; --whole-file and --no-whole-file. + + Use awk to pull out the 'speedup' number for each transfer. Make + sure it is >= x. + + -- -- + + +Test large files + + Sparse and non-sparse + + -- -- + + +Create mutator program for testing + + Insert bytes, delete bytes, swap blocks, ... + + -- -- + + +Create configure option to enable dangerous tests + + -- -- + + +Create pipe program for testing + + Create pipe program that makes slow/jerky connections for + testing Versions of read() and write() that corrupt the + stream, or abruptly fail + + -- -- + + +Create test makefile target for some tests + + Separate makefile target to run rough tests -- or perhaps + just run them every time? + + -- -- + +RELATED PROJECTS ----------------------------------------------------- + +rsyncsh + + Write a small emulation of interactive ftp as a Pythonn program + that calls rsync. Commands such as "cd", "ls", "ls *.c" etc map + fairly directly into rsync commands: it just needs to remember the + current host, directory and so on. We can probably even do + completion of remote filenames. + + -- -- + + +http://rsync.samba.org/rsync-and-debian/ + + + -- -- + + +rsyncable gzip patch + + Exhaustive, tortuous testing + + Cleanups? + + -- -- + + +rsyncsplit as alternative to real integration with gzip? + + -- -- + + +reverse rsync over HTTP Range + + Goswin Brederlow suggested this on Debian; I think tridge and I + talked about it previous in relation to rproxy. + + Addendum: It looks like someone is working on a version of this: + + http://zsync.moria.org.uk/ + + -- -- + diff --git a/rsync/access.c b/rsync/access.c new file mode 100644 index 0000000..df89d6d --- /dev/null +++ b/rsync/access.c @@ -0,0 +1,290 @@ +/* + * Routines to authenticate access to a daemon (hosts allow/deny). + * + * Copyright (C) 1998 Andrew Tridgell + * Copyright (C) 2004-2014 Wayne Davison + * + * 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, visit the http://fsf.org website. + */ + +#include "rsync.h" + +static int allow_forward_dns; + +extern const char undetermined_hostname[]; + +static int match_hostname(const char **host_ptr, const char *addr, const char *tok) +{ + struct hostent *hp; + unsigned int i; + const char *host = *host_ptr; + + if (!host || !*host) + return 0; + + /* First check if the reverse-DNS-determined hostname matches. */ + if (iwildmatch(tok, host)) + return 1; + + if (!allow_forward_dns) + return 0; + + /* Fail quietly if tok is an address or wildcarded entry, not a simple hostname. */ + if (!tok[strspn(tok, ".0123456789")] || tok[strcspn(tok, ":/*?[")]) + return 0; + + /* Now try forward-DNS on the token (config-specified hostname) and see if the IP matches. */ + if (!(hp = gethostbyname(tok))) + return 0; + + for (i = 0; hp->h_addr_list[i] != NULL; i++) { + if (strcmp(addr, inet_ntoa(*(struct in_addr*)(hp->h_addr_list[i]))) == 0) { + /* If reverse lookups are off, we'll use the conf-specified + * hostname in preference to UNDETERMINED. */ + if (host == undetermined_hostname) { + if (!(*host_ptr = strdup(tok))) + *host_ptr = undetermined_hostname; + } + return 1; + } + } + + return 0; +} + +static int match_binary(const char *b1, const char *b2, const char *mask, int addrlen) +{ + int i; + + for (i = 0; i < addrlen; i++) { + if ((b1[i] ^ b2[i]) & mask[i]) + return 0; + } + + return 1; +} + +static void make_mask(char *mask, int plen, int addrlen) +{ + int w, b; + + w = plen >> 3; + b = plen & 0x7; + + if (w) + memset(mask, 0xff, w); + if (w < addrlen) + mask[w] = 0xff & (0xff<<(8-b)); + if (w+1 < addrlen) + memset(mask+w+1, 0, addrlen-w-1); + + return; +} + +static int match_address(const char *addr, const char *tok) +{ + char *p; + struct addrinfo hints, *resa, *rest; + int gai; + int ret = 0; + int addrlen = 0; +#ifdef HAVE_STRTOL + long int bits; +#else + int bits; +#endif + char mask[16]; + char *a = NULL, *t = NULL; + + if (!addr || !*addr) + return 0; + + p = strchr(tok,'/'); + if (p) + *p = '\0'; + + /* Fail quietly if tok is a hostname, not an address. */ + if (tok[strspn(tok, ".0123456789")] && strchr(tok, ':') == NULL) { + if (p) + *p = '/'; + return 0; + } + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; +#ifdef AI_NUMERICHOST + hints.ai_flags = AI_NUMERICHOST; +#endif + + if (getaddrinfo(addr, NULL, &hints, &resa) != 0) { + if (p) + *p = '/'; + return 0; + } + + gai = getaddrinfo(tok, NULL, &hints, &rest); + if (p) + *p++ = '/'; + if (gai != 0) { + rprintf(FLOG, "error matching address %s: %s\n", + tok, gai_strerror(gai)); + freeaddrinfo(resa); + return 0; + } + + if (rest->ai_family != resa->ai_family) { + ret = 0; + goto out; + } + + switch(resa->ai_family) { + case PF_INET: + a = (char *)&((struct sockaddr_in *)resa->ai_addr)->sin_addr; + t = (char *)&((struct sockaddr_in *)rest->ai_addr)->sin_addr; + addrlen = 4; + + break; + +#ifdef INET6 + case PF_INET6: + { + struct sockaddr_in6 *sin6a, *sin6t; + + sin6a = (struct sockaddr_in6 *)resa->ai_addr; + sin6t = (struct sockaddr_in6 *)rest->ai_addr; + + a = (char *)&sin6a->sin6_addr; + t = (char *)&sin6t->sin6_addr; + + addrlen = 16; + +#ifdef HAVE_SOCKADDR_IN6_SCOPE_ID + if (sin6t->sin6_scope_id && + sin6a->sin6_scope_id != sin6t->sin6_scope_id) { + ret = 0; + goto out; + } +#endif + + break; + } +#endif + default: + rprintf(FLOG, "unknown family %u\n", rest->ai_family); + ret = 0; + goto out; + } + + bits = -1; + if (p) { + if (inet_pton(resa->ai_addr->sa_family, p, mask) <= 0) { +#ifdef HAVE_STRTOL + char *ep = NULL; +#else + unsigned char *pp; +#endif + +#ifdef HAVE_STRTOL + bits = strtol(p, &ep, 10); + if (!*p || *ep) { + rprintf(FLOG, "malformed mask in %s\n", tok); + ret = 0; + goto out; + } +#else + for (pp = (unsigned char *)p; *pp; pp++) { + if (!isascii(*pp) || !isdigit(*pp)) { + rprintf(FLOG, "malformed mask in %s\n", tok); + ret = 0; + goto out; + } + } + bits = atoi(p); +#endif + if (bits == 0) { + ret = 1; + goto out; + } + if (bits < 0 || bits > (addrlen << 3)) { + rprintf(FLOG, "malformed mask in %s\n", tok); + ret = 0; + goto out; + } + } + } else { + bits = 128; + } + + if (bits >= 0) + make_mask(mask, bits, addrlen); + + ret = match_binary(a, t, mask, addrlen); + + out: + freeaddrinfo(resa); + freeaddrinfo(rest); + return ret; +} + +static int access_match(const char *list, const char *addr, const char **host_ptr) +{ + char *tok; + char *list2 = strdup(list); + + if (!list2) + out_of_memory("access_match"); + + strlower(list2); + + for (tok = strtok(list2, " ,\t"); tok; tok = strtok(NULL, " ,\t")) { + if (match_hostname(host_ptr, addr, tok) || match_address(addr, tok)) { + free(list2); + return 1; + } + } + + free(list2); + return 0; +} + +int allow_access(const char *addr, const char **host_ptr, int i) +{ + const char *allow_list = lp_hosts_allow(i); + const char *deny_list = lp_hosts_deny(i); + + if (allow_list && !*allow_list) + allow_list = NULL; + if (deny_list && !*deny_list) + deny_list = NULL; + + allow_forward_dns = lp_forward_lookup(i); + + /* If we match an allow-list item, we always allow access. */ + if (allow_list) { + if (access_match(allow_list, addr, host_ptr)) + return 1; + /* For an allow-list w/o a deny-list, disallow non-matches. */ + if (!deny_list) + return 0; + } + + /* If we match a deny-list item (and got past any allow-list + * items), we always disallow access. */ + if (deny_list && access_match(deny_list, addr, host_ptr)) + return 0; + + /* Allow all other access. */ + return 1; +} diff --git a/rsync/aclocal.m4 b/rsync/aclocal.m4 new file mode 100644 index 0000000..d489b20 --- /dev/null +++ b/rsync/aclocal.m4 @@ -0,0 +1,92 @@ +dnl AC_VALIDATE_CACHE_SYSTEM_TYPE[(cmd)] +dnl if the cache file is inconsistent with the current host, +dnl target and build system types, execute CMD or print a default +dnl error message. +AC_DEFUN(AC_VALIDATE_CACHE_SYSTEM_TYPE, [ + AC_REQUIRE([AC_CANONICAL_SYSTEM]) + AC_MSG_CHECKING([config.cache system type]) + if { test x"${ac_cv_host_system_type+set}" = x"set" && + test x"$ac_cv_host_system_type" != x"$host"; } || + { test x"${ac_cv_build_system_type+set}" = x"set" && + test x"$ac_cv_build_system_type" != x"$build"; } || + { test x"${ac_cv_target_system_type+set}" = x"set" && + test x"$ac_cv_target_system_type" != x"$target"; }; then + AC_MSG_RESULT([different]) + ifelse($#, 1, [$1], + [AC_MSG_ERROR(["you must remove config.cache and restart configure"])]) + else + AC_MSG_RESULT([same]) + fi + ac_cv_host_system_type="$host" + ac_cv_build_system_type="$build" + ac_cv_target_system_type="$target" +]) + +dnl Check for socklen_t: historically on BSD it is an int, and in +dnl POSIX 1g it is a type of its own, but some platforms use different +dnl types for the argument to getsockopt, getpeername, etc. So we +dnl have to test to find something that will work. + +dnl This is no good, because passing the wrong pointer on C compilers is +dnl likely to only generate a warning, not an error. We don't call this at +dnl the moment. + +AC_DEFUN([TYPE_SOCKLEN_T], +[ + AC_CHECK_TYPE([socklen_t], ,[ + AC_MSG_CHECKING([for socklen_t equivalent]) + AC_CACHE_VAL([rsync_cv_socklen_t_equiv], + [ + # Systems have either "struct sockaddr *" or + # "void *" as the second argument to getpeername + rsync_cv_socklen_t_equiv= + for arg2 in "struct sockaddr" void; do + for t in int size_t unsigned long "unsigned long"; do + AC_TRY_COMPILE([ +#include +#include + + int getpeername (int, $arg2 *, $t *); + ],[ + $t len; + getpeername(0,0,&len); + ],[ + rsync_cv_socklen_t_equiv="$t" + break + ]) + done + done + + if test "x$rsync_cv_socklen_t_equiv" = x; then + AC_MSG_ERROR([Cannot find a type to use in place of socklen_t]) + fi + ]) + AC_MSG_RESULT($rsync_cv_socklen_t_equiv) + AC_DEFINE_UNQUOTED(socklen_t, $rsync_cv_socklen_t_equiv, + [type to use in place of socklen_t if not defined])], + [#include +#include ]) +]) + +dnl AC_HAVE_TYPE(TYPE,INCLUDES) +AC_DEFUN([AC_HAVE_TYPE], [ +AC_REQUIRE([AC_HEADER_STDC]) +cv=`echo "$1" | sed 'y%./+- %__p__%'` +AC_MSG_CHECKING(for $1) +AC_CACHE_VAL([ac_cv_type_$cv], +AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ +AC_INCLUDES_DEFAULT +$2]], +[[$1 foo;]])], +[eval "ac_cv_type_$cv=yes"], +[eval "ac_cv_type_$cv=no"]))dnl +ac_foo=`eval echo \\$ac_cv_type_$cv` +AC_MSG_RESULT($ac_foo) +if test "$ac_foo" = yes; then + ac_tr_hdr=HAVE_`echo $1 | sed 'y%abcdefghijklmnopqrstuvwxyz./- %ABCDEFGHIJKLMNOPQRSTUVWXYZ____%'` +if false; then + AC_CHECK_TYPES($1) +fi + AC_DEFINE_UNQUOTED($ac_tr_hdr, 1, [Define if you have type `$1']) +fi +]) diff --git a/rsync/acls.c b/rsync/acls.c new file mode 100644 index 0000000..ec1afc4 --- /dev/null +++ b/rsync/acls.c @@ -0,0 +1,1155 @@ +/* + * Handle passing Access Control Lists between systems. + * + * Copyright (C) 1996 Andrew Tridgell + * Copyright (C) 1996 Paul Mackerras + * Copyright (C) 2006-2014 Wayne Davison + * + * 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, visit the http://fsf.org website. + */ + +#include "rsync.h" +#include "lib/sysacls.h" + +#ifdef SUPPORT_ACLS + +extern int dry_run; +extern int am_root; +extern int read_only; +extern int list_only; +extern int orig_umask; +extern int numeric_ids; +extern int inc_recurse; +extern int preserve_devices; +extern int preserve_specials; + +/* Flags used to indicate what items are being transmitted for an entry. */ +#define XMIT_USER_OBJ (1<<0) +#define XMIT_GROUP_OBJ (1<<1) +#define XMIT_MASK_OBJ (1<<2) +#define XMIT_OTHER_OBJ (1<<3) +#define XMIT_NAME_LIST (1<<4) + +#define NO_ENTRY ((uchar)0x80) /* Default value of a NON-name-list entry. */ + +#define NAME_IS_USER (1u<<31) /* Bit used only on a name-list entry. */ + +/* When we send the access bits over the wire, we shift them 2 bits to the + * left and use the lower 2 bits as flags (relevant only to a name entry). + * This makes the protocol more efficient than sending a value that would + * be likely to have its hightest bits set. */ +#define XFLAG_NAME_FOLLOWS 0x0001u +#define XFLAG_NAME_IS_USER 0x0002u + +/* === ACL structures === */ + +typedef struct { + id_t id; + uint32 access; +} id_access; + +typedef struct { + id_access *idas; + int count; +} ida_entries; + +typedef struct { + char *name; + uchar len; +} idname; + +typedef struct rsync_acl { + ida_entries names; + /* These will be NO_ENTRY if there's no such entry. */ + uchar user_obj; + uchar group_obj; + uchar mask_obj; + uchar other_obj; +} rsync_acl; + +typedef struct { + rsync_acl racl; + SMB_ACL_T sacl; +} acl_duo; + +static const rsync_acl empty_rsync_acl = { + {NULL, 0}, NO_ENTRY, NO_ENTRY, NO_ENTRY, NO_ENTRY +}; + +static item_list access_acl_list = EMPTY_ITEM_LIST; +static item_list default_acl_list = EMPTY_ITEM_LIST; + +static size_t prior_access_count = (size_t)-1; +static size_t prior_default_count = (size_t)-1; + +/* === Calculations on ACL types === */ + +static const char *str_acl_type(SMB_ACL_TYPE_T type) +{ + switch (type) { + case SMB_ACL_TYPE_ACCESS: +#ifdef HAVE_OSX_ACLS + return "ACL_TYPE_EXTENDED"; +#else + return "ACL_TYPE_ACCESS"; +#endif + case SMB_ACL_TYPE_DEFAULT: + return "ACL_TYPE_DEFAULT"; + default: + break; + } + return "unknown ACL type!"; +} + +static int calc_sacl_entries(const rsync_acl *racl) +{ + /* A System ACL always gets user/group/other permission entries. */ + return racl->names.count +#ifdef ACLS_NEED_MASK + + 1 +#else + + (racl->mask_obj != NO_ENTRY) +#endif + + 3; +} + +/* Extracts and returns the permission bits from the ACL. This cannot be + * called on an rsync_acl that has NO_ENTRY in any spot but the mask. */ +static int rsync_acl_get_perms(const rsync_acl *racl) +{ + return (racl->user_obj << 6) + + ((racl->mask_obj != NO_ENTRY ? racl->mask_obj : racl->group_obj) << 3) + + racl->other_obj; +} + +/* Removes the permission-bit entries from the ACL because these + * can be reconstructed from the file's mode. */ +static void rsync_acl_strip_perms(stat_x *sxp) +{ + rsync_acl *racl = sxp->acc_acl; + + racl->user_obj = NO_ENTRY; + if (racl->mask_obj == NO_ENTRY) + racl->group_obj = NO_ENTRY; + else { + int group_perms = (sxp->st.st_mode >> 3) & 7; + if (racl->group_obj == group_perms) + racl->group_obj = NO_ENTRY; +#ifndef HAVE_SOLARIS_ACLS + if (racl->names.count != 0 && racl->mask_obj == group_perms) + racl->mask_obj = NO_ENTRY; +#endif + } + racl->other_obj = NO_ENTRY; +} + +/* Given an empty rsync_acl, fake up the permission bits. */ +static void rsync_acl_fake_perms(rsync_acl *racl, mode_t mode) +{ + racl->user_obj = (mode >> 6) & 7; + racl->group_obj = (mode >> 3) & 7; + racl->other_obj = mode & 7; +} + +/* === Rsync ACL functions === */ + +static rsync_acl *create_racl(void) +{ + rsync_acl *racl = new(rsync_acl); + + if (!racl) + out_of_memory("create_racl"); + *racl = empty_rsync_acl; + + return racl; +} + +static BOOL ida_entries_equal(const ida_entries *ial1, const ida_entries *ial2) +{ + id_access *ida1, *ida2; + int count = ial1->count; + if (count != ial2->count) + return False; + ida1 = ial1->idas; + ida2 = ial2->idas; + for (; count--; ida1++, ida2++) { + if (ida1->access != ida2->access || ida1->id != ida2->id) + return False; + } + return True; +} + +static BOOL rsync_acl_equal(const rsync_acl *racl1, const rsync_acl *racl2) +{ + return racl1->user_obj == racl2->user_obj + && racl1->group_obj == racl2->group_obj + && racl1->mask_obj == racl2->mask_obj + && racl1->other_obj == racl2->other_obj + && ida_entries_equal(&racl1->names, &racl2->names); +} + +/* Are the extended (non-permission-bit) entries equal? If so, the rest of + * the ACL will be handled by the normal mode-preservation code. This is + * only meaningful for access ACLs! Note: the 1st arg is a fully-populated + * rsync_acl, but the 2nd parameter can be a condensed rsync_acl, which means + * that it might have several of its permission objects set to NO_ENTRY. */ +static BOOL rsync_acl_equal_enough(const rsync_acl *racl1, + const rsync_acl *racl2, mode_t m) +{ + if ((racl1->mask_obj ^ racl2->mask_obj) & NO_ENTRY) + return False; /* One has a mask and the other doesn't */ + + /* When there's a mask, the group_obj becomes an extended entry. */ + if (racl1->mask_obj != NO_ENTRY) { + /* A condensed rsync_acl with a mask can only have no + * group_obj when it was identical to the mask. This + * means that it was also identical to the group attrs + * from the mode. */ + if (racl2->group_obj == NO_ENTRY) { + if (racl1->group_obj != ((m >> 3) & 7)) + return False; + } else if (racl1->group_obj != racl2->group_obj) + return False; + } + return ida_entries_equal(&racl1->names, &racl2->names); +} + +static void rsync_acl_free(rsync_acl *racl) +{ + if (racl->names.idas) + free(racl->names.idas); + *racl = empty_rsync_acl; +} + +void free_acl(stat_x *sxp) +{ + if (sxp->acc_acl) { + rsync_acl_free(sxp->acc_acl); + free(sxp->acc_acl); + sxp->acc_acl = NULL; + } + if (sxp->def_acl) { + rsync_acl_free(sxp->def_acl); + free(sxp->def_acl); + sxp->def_acl = NULL; + } +} + +#ifdef SMB_ACL_NEED_SORT +static int id_access_sorter(const void *r1, const void *r2) +{ + id_access *ida1 = (id_access *)r1; + id_access *ida2 = (id_access *)r2; + id_t rid1 = ida1->id, rid2 = ida2->id; + if ((ida1->access ^ ida2->access) & NAME_IS_USER) + return ida1->access & NAME_IS_USER ? -1 : 1; + return rid1 == rid2 ? 0 : rid1 < rid2 ? -1 : 1; +} +#endif + +/* === System ACLs === */ + +/* Unpack system ACL -> rsync ACL verbatim. Return whether we succeeded. */ +static BOOL unpack_smb_acl(SMB_ACL_T sacl, rsync_acl *racl) +{ + static item_list temp_ida_list = EMPTY_ITEM_LIST; + SMB_ACL_ENTRY_T entry; + const char *errfun; + int rc; + + errfun = "sys_acl_get_entry"; + for (rc = sys_acl_get_entry(sacl, SMB_ACL_FIRST_ENTRY, &entry); + rc == 1; + rc = sys_acl_get_entry(sacl, SMB_ACL_NEXT_ENTRY, &entry)) { + SMB_ACL_TAG_T tag_type; + uint32 access; + id_t g_u_id; + id_access *ida; + if ((rc = sys_acl_get_info(entry, &tag_type, &access, &g_u_id)) != 0) { + errfun = "sys_acl_get_info"; + break; + } + /* continue == done with entry; break == store in temporary ida list */ + switch (tag_type) { +#ifndef HAVE_OSX_ACLS + case SMB_ACL_USER_OBJ: + if (racl->user_obj == NO_ENTRY) + racl->user_obj = access; + else + rprintf(FINFO, "unpack_smb_acl: warning: duplicate USER_OBJ entry ignored\n"); + continue; + case SMB_ACL_GROUP_OBJ: + if (racl->group_obj == NO_ENTRY) + racl->group_obj = access; + else + rprintf(FINFO, "unpack_smb_acl: warning: duplicate GROUP_OBJ entry ignored\n"); + continue; + case SMB_ACL_MASK: + if (racl->mask_obj == NO_ENTRY) + racl->mask_obj = access; + else + rprintf(FINFO, "unpack_smb_acl: warning: duplicate MASK entry ignored\n"); + continue; + case SMB_ACL_OTHER: + if (racl->other_obj == NO_ENTRY) + racl->other_obj = access; + else + rprintf(FINFO, "unpack_smb_acl: warning: duplicate OTHER entry ignored\n"); + continue; +#endif + case SMB_ACL_USER: + access |= NAME_IS_USER; + break; + case SMB_ACL_GROUP: + break; + default: + rprintf(FINFO, "unpack_smb_acl: warning: entry with unrecognized tag type ignored\n"); + continue; + } + ida = EXPAND_ITEM_LIST(&temp_ida_list, id_access, -10); + ida->id = g_u_id; + ida->access = access; + } + if (rc) { + rsyserr(FERROR_XFER, errno, "unpack_smb_acl: %s()", errfun); + rsync_acl_free(racl); + return False; + } + + /* Transfer the count id_access items out of the temp_ida_list + * into the names ida_entries list in racl. */ + if (temp_ida_list.count) { +#ifdef SMB_ACL_NEED_SORT + if (temp_ida_list.count > 1) { + qsort(temp_ida_list.items, temp_ida_list.count, + sizeof (id_access), id_access_sorter); + } +#endif + if (!(racl->names.idas = new_array(id_access, temp_ida_list.count))) + out_of_memory("unpack_smb_acl"); + memcpy(racl->names.idas, temp_ida_list.items, + temp_ida_list.count * sizeof (id_access)); + } else + racl->names.idas = NULL; + + racl->names.count = temp_ida_list.count; + + /* Truncate the temporary list now that its idas have been saved. */ + temp_ida_list.count = 0; + + return True; +} + +/* Synactic sugar for system calls */ + +#define CALL_OR_ERROR(func,args,str) \ + do { \ + if (func args) { \ + errfun = str; \ + goto error_exit; \ + } \ + } while (0) + +#define COE(func,args) CALL_OR_ERROR(func,args,#func) +#define COE2(func,args) CALL_OR_ERROR(func,args,NULL) + +#ifndef HAVE_OSX_ACLS +/* Store the permissions in the system ACL entry. */ +static int store_access_in_entry(uint32 access, SMB_ACL_ENTRY_T entry) +{ + if (sys_acl_set_access_bits(entry, access)) { + rsyserr(FERROR_XFER, errno, "store_access_in_entry sys_acl_set_access_bits()"); + return -1; + } + return 0; +} +#endif + +/* Pack rsync ACL -> system ACL verbatim. Return whether we succeeded. */ +static BOOL pack_smb_acl(SMB_ACL_T *smb_acl, const rsync_acl *racl) +{ +#ifdef ACLS_NEED_MASK + uchar mask_bits; +#endif + size_t count; + id_access *ida; + const char *errfun = NULL; + SMB_ACL_ENTRY_T entry; + + if (!(*smb_acl = sys_acl_init(calc_sacl_entries(racl)))) { + rsyserr(FERROR_XFER, errno, "pack_smb_acl: sys_acl_init()"); + return False; + } + +#ifndef HAVE_OSX_ACLS + COE( sys_acl_create_entry,(smb_acl, &entry) ); + COE( sys_acl_set_info,(entry, SMB_ACL_USER_OBJ, racl->user_obj & ~NO_ENTRY, 0) ); +#endif + + for (ida = racl->names.idas, count = racl->names.count; count; ida++, count--) { +#ifdef SMB_ACL_NEED_SORT + if (!(ida->access & NAME_IS_USER)) + break; +#endif + COE( sys_acl_create_entry,(smb_acl, &entry) ); + COE( sys_acl_set_info, + (entry, + ida->access & NAME_IS_USER ? SMB_ACL_USER : SMB_ACL_GROUP, + ida->access & ~NAME_IS_USER, ida->id) ); + } + +#ifndef HAVE_OSX_ACLS + COE( sys_acl_create_entry,(smb_acl, &entry) ); + COE( sys_acl_set_info,(entry, SMB_ACL_GROUP_OBJ, racl->group_obj & ~NO_ENTRY, 0) ); + +#ifdef SMB_ACL_NEED_SORT + for ( ; count; ida++, count--) { + COE( sys_acl_create_entry,(smb_acl, &entry) ); + COE( sys_acl_set_info,(entry, SMB_ACL_GROUP, ida->access, ida->id) ); + } +#endif + +#ifdef ACLS_NEED_MASK + mask_bits = racl->mask_obj == NO_ENTRY ? racl->group_obj & ~NO_ENTRY : racl->mask_obj; + COE( sys_acl_create_entry,(smb_acl, &entry) ); + COE( sys_acl_set_info,(entry, SMB_ACL_MASK, mask_bits, 0) ); +#else + if (racl->mask_obj != NO_ENTRY) { + COE( sys_acl_create_entry,(smb_acl, &entry) ); + COE( sys_acl_set_info,(entry, SMB_ACL_MASK, racl->mask_obj, 0) ); + } +#endif + + COE( sys_acl_create_entry,(smb_acl, &entry) ); + COE( sys_acl_set_info,(entry, SMB_ACL_OTHER, racl->other_obj & ~NO_ENTRY, 0) ); +#endif + +#ifdef DEBUG + if (sys_acl_valid(*smb_acl) < 0) + rprintf(FERROR_XFER, "pack_smb_acl: warning: system says the ACL I packed is invalid\n"); +#endif + + return True; + + error_exit: + if (errfun) { + rsyserr(FERROR_XFER, errno, "pack_smb_acl %s()", errfun); + } + sys_acl_free_acl(*smb_acl); + return False; +} + +static int find_matching_rsync_acl(const rsync_acl *racl, SMB_ACL_TYPE_T type, + const item_list *racl_list) +{ + static int access_match = -1, default_match = -1; + int *match = type == SMB_ACL_TYPE_ACCESS ? &access_match : &default_match; + size_t count = racl_list->count; + + /* If this is the first time through or we didn't match the last + * time, then start at the end of the list, which should be the + * best place to start hunting. */ + if (*match == -1) + *match = racl_list->count - 1; + while (count--) { + rsync_acl *base = racl_list->items; + if (rsync_acl_equal(base + *match, racl)) + return *match; + if (!(*match)--) + *match = racl_list->count - 1; + } + + *match = -1; + return *match; +} + +static int get_rsync_acl(const char *fname, rsync_acl *racl, + SMB_ACL_TYPE_T type, mode_t mode) +{ + SMB_ACL_T sacl; + +#ifdef SUPPORT_XATTRS + /* --fake-super support: load ACLs from an xattr. */ + if (am_root < 0) { + char *buf; + size_t len; + int cnt; + + if ((buf = get_xattr_acl(fname, type == SMB_ACL_TYPE_ACCESS, &len)) == NULL) + return 0; + cnt = (len - 4*4) / (4+4); + if (len < 4*4 || len != (size_t)cnt*(4+4) + 4*4) { + free(buf); + return -1; + } + + racl->user_obj = IVAL(buf, 0); + if (racl->user_obj == NO_ENTRY) + racl->user_obj = (mode >> 6) & 7; + racl->group_obj = IVAL(buf, 4); + if (racl->group_obj == NO_ENTRY) + racl->group_obj = (mode >> 3) & 7; + racl->mask_obj = IVAL(buf, 8); + racl->other_obj = IVAL(buf, 12); + if (racl->other_obj == NO_ENTRY) + racl->other_obj = mode & 7; + + if (cnt) { + char *bp = buf + 4*4; + id_access *ida; + if (!(ida = racl->names.idas = new_array(id_access, cnt))) + out_of_memory("get_rsync_acl"); + racl->names.count = cnt; + for ( ; cnt--; ida++, bp += 4+4) { + ida->id = IVAL(bp, 0); + ida->access = IVAL(bp, 4); + } + } + free(buf); + return 0; + } +#endif + + if ((sacl = sys_acl_get_file(fname, type)) != 0) { + BOOL ok = unpack_smb_acl(sacl, racl); + + sys_acl_free_acl(sacl); + if (!ok) { + return -1; + } + } else if (no_acl_syscall_error(errno)) { + /* ACLs are not supported, so pretend we have a basic ACL. */ + if (type == SMB_ACL_TYPE_ACCESS) + rsync_acl_fake_perms(racl, mode); + } else { + rsyserr(FERROR_XFER, errno, "get_acl: sys_acl_get_file(%s, %s)", + fname, str_acl_type(type)); + return -1; + } + + return 0; +} + +/* Return the Access Control List for the given filename. */ +int get_acl(const char *fname, stat_x *sxp) +{ + sxp->acc_acl = create_racl(); + + if (S_ISREG(sxp->st.st_mode) || S_ISDIR(sxp->st.st_mode)) { + /* Everyone supports this. */ + } else if (S_ISLNK(sxp->st.st_mode)) { + return 0; + } else if (IS_SPECIAL(sxp->st.st_mode)) { +#ifndef NO_SPECIAL_ACLS + if (!preserve_specials) +#endif + return 0; + } else if (IS_DEVICE(sxp->st.st_mode)) { +#ifndef NO_DEVICE_ACLS + if (!preserve_devices) +#endif + return 0; + } else if (IS_MISSING_FILE(sxp->st)) + return 0; + + if (get_rsync_acl(fname, sxp->acc_acl, SMB_ACL_TYPE_ACCESS, + sxp->st.st_mode) < 0) { + free_acl(sxp); + return -1; + } + + if (S_ISDIR(sxp->st.st_mode)) { + sxp->def_acl = create_racl(); + if (get_rsync_acl(fname, sxp->def_acl, SMB_ACL_TYPE_DEFAULT, + sxp->st.st_mode) < 0) { + free_acl(sxp); + return -1; + } + } + + return 0; +} + +/* === Send functions === */ + +/* Send the ida list over the file descriptor. */ +static void send_ida_entries(int f, const ida_entries *idal) +{ + id_access *ida; + size_t count = idal->count; + + write_varint(f, idal->count); + + for (ida = idal->idas; count--; ida++) { + uint32 xbits = ida->access << 2; + const char *name; + if (ida->access & NAME_IS_USER) { + xbits |= XFLAG_NAME_IS_USER; + name = numeric_ids ? NULL : add_uid(ida->id); + } else + name = numeric_ids ? NULL : add_gid(ida->id); + write_varint(f, ida->id); + if (inc_recurse && name) { + int len = strlen(name); + write_varint(f, xbits | XFLAG_NAME_FOLLOWS); + write_byte(f, len); + write_buf(f, name, len); + } else + write_varint(f, xbits); + } +} + +static void send_rsync_acl(int f, rsync_acl *racl, SMB_ACL_TYPE_T type, + item_list *racl_list) +{ + int ndx = find_matching_rsync_acl(racl, type, racl_list); + + /* Send 0 (-1 + 1) to indicate that literal ACL data follows. */ + write_varint(f, ndx + 1); + + if (ndx < 0) { + rsync_acl *new_racl = EXPAND_ITEM_LIST(racl_list, rsync_acl, 1000); + uchar flags = 0; + + if (racl->user_obj != NO_ENTRY) + flags |= XMIT_USER_OBJ; + if (racl->group_obj != NO_ENTRY) + flags |= XMIT_GROUP_OBJ; + if (racl->mask_obj != NO_ENTRY) + flags |= XMIT_MASK_OBJ; + if (racl->other_obj != NO_ENTRY) + flags |= XMIT_OTHER_OBJ; + if (racl->names.count) + flags |= XMIT_NAME_LIST; + + write_byte(f, flags); + + if (flags & XMIT_USER_OBJ) + write_varint(f, racl->user_obj); + if (flags & XMIT_GROUP_OBJ) + write_varint(f, racl->group_obj); + if (flags & XMIT_MASK_OBJ) + write_varint(f, racl->mask_obj); + if (flags & XMIT_OTHER_OBJ) + write_varint(f, racl->other_obj); + if (flags & XMIT_NAME_LIST) + send_ida_entries(f, &racl->names); + + /* Give the allocated data to the new list object. */ + *new_racl = *racl; + *racl = empty_rsync_acl; + } +} + +/* Send the ACL from the stat_x structure down the indicated file descriptor. + * This also frees the ACL data. */ +void send_acl(int f, stat_x *sxp) +{ + if (!sxp->acc_acl) { + sxp->acc_acl = create_racl(); + rsync_acl_fake_perms(sxp->acc_acl, sxp->st.st_mode); + } + /* Avoid sending values that can be inferred from other data. */ + rsync_acl_strip_perms(sxp); + + send_rsync_acl(f, sxp->acc_acl, SMB_ACL_TYPE_ACCESS, &access_acl_list); + + if (S_ISDIR(sxp->st.st_mode)) { + if (!sxp->def_acl) + sxp->def_acl = create_racl(); + + send_rsync_acl(f, sxp->def_acl, SMB_ACL_TYPE_DEFAULT, &default_acl_list); + } +} + +/* === Receive functions === */ + +static uint32 recv_acl_access(int f, uchar *name_follows_ptr) +{ + uint32 access = read_varint(f); + + if (name_follows_ptr) { + int flags = access & 3; + access >>= 2; + if (am_root >= 0 && access & ~SMB_ACL_VALID_NAME_BITS) + goto value_error; + if (flags & XFLAG_NAME_FOLLOWS) + *name_follows_ptr = 1; + else + *name_follows_ptr = 0; + if (flags & XFLAG_NAME_IS_USER) + access |= NAME_IS_USER; + } else if (am_root >= 0 && access & ~SMB_ACL_VALID_OBJ_BITS) { + value_error: + rprintf(FERROR_XFER, "recv_acl_access: value out of range: %x\n", + access); + exit_cleanup(RERR_STREAMIO); + } + + return access; +} + +static uchar recv_ida_entries(int f, ida_entries *ent) +{ + uchar computed_mask_bits = 0; + int i, count = read_varint(f); + + if (count) { + if (!(ent->idas = new_array(id_access, count))) + out_of_memory("recv_ida_entries"); + } else + ent->idas = NULL; + + ent->count = count; + + for (i = 0; i < count; i++) { + uchar has_name; + id_t id = read_varint(f); + uint32 access = recv_acl_access(f, &has_name); + + if (has_name) { + if (access & NAME_IS_USER) + id = recv_user_name(f, id); + else + id = recv_group_name(f, id, NULL); + } else if (access & NAME_IS_USER) { + if (inc_recurse && am_root && !numeric_ids) + id = match_uid(id); + } else { + if (inc_recurse && (!am_root || !numeric_ids)) + id = match_gid(id, NULL); + } + + ent->idas[i].id = id; + ent->idas[i].access = access; + computed_mask_bits |= access; + } + + return computed_mask_bits & ~NO_ENTRY; +} + +static int recv_rsync_acl(int f, item_list *racl_list, SMB_ACL_TYPE_T type, mode_t mode) +{ + uchar computed_mask_bits = 0; + acl_duo *duo_item; + uchar flags; + int ndx = read_varint(f); + + if (ndx < 0 || (size_t)ndx > racl_list->count) { + rprintf(FERROR_XFER, "recv_acl_index: %s ACL index %d > %d\n", + str_acl_type(type), ndx, (int)racl_list->count); + exit_cleanup(RERR_STREAMIO); + } + + if (ndx != 0) + return ndx - 1; + + ndx = racl_list->count; + duo_item = EXPAND_ITEM_LIST(racl_list, acl_duo, 1000); + duo_item->racl = empty_rsync_acl; + + flags = read_byte(f); + + if (flags & XMIT_USER_OBJ) + duo_item->racl.user_obj = recv_acl_access(f, NULL); + if (flags & XMIT_GROUP_OBJ) + duo_item->racl.group_obj = recv_acl_access(f, NULL); + if (flags & XMIT_MASK_OBJ) + duo_item->racl.mask_obj = recv_acl_access(f, NULL); + if (flags & XMIT_OTHER_OBJ) + duo_item->racl.other_obj = recv_acl_access(f, NULL); + if (flags & XMIT_NAME_LIST) + computed_mask_bits |= recv_ida_entries(f, &duo_item->racl.names); + +#ifdef HAVE_OSX_ACLS + /* If we received a superfluous mask, throw it away. */ + duo_item->racl.mask_obj = NO_ENTRY; +#else + if (duo_item->racl.names.count && duo_item->racl.mask_obj == NO_ENTRY) { + /* Mask must be non-empty with lists. */ + if (type == SMB_ACL_TYPE_ACCESS) + computed_mask_bits = (mode >> 3) & 7; + else + computed_mask_bits |= duo_item->racl.group_obj & ~NO_ENTRY; + duo_item->racl.mask_obj = computed_mask_bits; + } +#endif + + duo_item->sacl = NULL; + + return ndx; +} + +/* Receive the ACL info the sender has included for this file-list entry. */ +void receive_acl(int f, struct file_struct *file) +{ + F_ACL(file) = recv_rsync_acl(f, &access_acl_list, SMB_ACL_TYPE_ACCESS, file->mode); + + if (S_ISDIR(file->mode)) + F_DIR_DEFACL(file) = recv_rsync_acl(f, &default_acl_list, SMB_ACL_TYPE_DEFAULT, 0); +} + +static int cache_rsync_acl(rsync_acl *racl, SMB_ACL_TYPE_T type, item_list *racl_list) +{ + int ndx; + + if (!racl) + ndx = -1; + else if ((ndx = find_matching_rsync_acl(racl, type, racl_list)) == -1) { + acl_duo *new_duo; + ndx = racl_list->count; + new_duo = EXPAND_ITEM_LIST(racl_list, acl_duo, 1000); + new_duo->racl = *racl; + new_duo->sacl = NULL; + *racl = empty_rsync_acl; + } + + return ndx; +} + +/* Turn the ACL data in stat_x into cached ACL data, setting the index + * values in the file struct. */ +void cache_tmp_acl(struct file_struct *file, stat_x *sxp) +{ + if (prior_access_count == (size_t)-1) + prior_access_count = access_acl_list.count; + + F_ACL(file) = cache_rsync_acl(sxp->acc_acl, + SMB_ACL_TYPE_ACCESS, &access_acl_list); + + if (S_ISDIR(sxp->st.st_mode)) { + if (prior_default_count == (size_t)-1) + prior_default_count = default_acl_list.count; + F_DIR_DEFACL(file) = cache_rsync_acl(sxp->def_acl, + SMB_ACL_TYPE_DEFAULT, &default_acl_list); + } +} + +static void uncache_duo_acls(item_list *duo_list, size_t start) +{ + acl_duo *duo_item = duo_list->items; + acl_duo *duo_start = duo_item + start; + + duo_item += duo_list->count; + duo_list->count = start; + + while (duo_item-- > duo_start) { + rsync_acl_free(&duo_item->racl); + if (duo_item->sacl) + sys_acl_free_acl(duo_item->sacl); + } +} + +void uncache_tmp_acls(void) +{ + if (prior_access_count != (size_t)-1) { + uncache_duo_acls(&access_acl_list, prior_access_count); + prior_access_count = (size_t)-1; + } + + if (prior_default_count != (size_t)-1) { + uncache_duo_acls(&default_acl_list, prior_default_count); + prior_default_count = (size_t)-1; + } +} + +#ifndef HAVE_OSX_ACLS +static mode_t change_sacl_perms(SMB_ACL_T sacl, rsync_acl *racl, mode_t old_mode, mode_t mode) +{ + SMB_ACL_ENTRY_T entry; + const char *errfun; + int rc; + + if (S_ISDIR(mode)) { + /* If the sticky bit is going on, it's not safe to allow all + * the new ACL to go into effect before it gets set. */ +#ifdef SMB_ACL_LOSES_SPECIAL_MODE_BITS + if (mode & S_ISVTX) + mode &= ~0077; +#else + if (mode & S_ISVTX && !(old_mode & S_ISVTX)) + mode &= ~0077; + } else { + /* If setuid or setgid is going off, it's not safe to allow all + * the new ACL to go into effect before they get cleared. */ + if ((old_mode & S_ISUID && !(mode & S_ISUID)) + || (old_mode & S_ISGID && !(mode & S_ISGID))) + mode &= ~0077; +#endif + } + + errfun = "sys_acl_get_entry"; + for (rc = sys_acl_get_entry(sacl, SMB_ACL_FIRST_ENTRY, &entry); + rc == 1; + rc = sys_acl_get_entry(sacl, SMB_ACL_NEXT_ENTRY, &entry)) { + SMB_ACL_TAG_T tag_type; + if ((rc = sys_acl_get_tag_type(entry, &tag_type)) != 0) { + errfun = "sys_acl_get_tag_type"; + break; + } + switch (tag_type) { + case SMB_ACL_USER_OBJ: + COE2( store_access_in_entry,((mode >> 6) & 7, entry) ); + break; + case SMB_ACL_GROUP_OBJ: + /* group is only empty when identical to group perms. */ + if (racl->group_obj != NO_ENTRY) + break; + COE2( store_access_in_entry,((mode >> 3) & 7, entry) ); + break; + case SMB_ACL_MASK: +#ifndef HAVE_SOLARIS_ACLS +#ifndef ACLS_NEED_MASK + /* mask is only empty when we don't need it. */ + if (racl->mask_obj == NO_ENTRY) + break; +#endif + COE2( store_access_in_entry,((mode >> 3) & 7, entry) ); +#endif + break; + case SMB_ACL_OTHER: + COE2( store_access_in_entry,(mode & 7, entry) ); + break; + } + } + if (rc) { + error_exit: + if (errfun) { + rsyserr(FERROR_XFER, errno, "change_sacl_perms: %s()", + errfun); + } + return (mode_t)-1; + } + +#ifdef SMB_ACL_LOSES_SPECIAL_MODE_BITS + /* Ensure that chmod() will be called to restore any lost setid bits. */ + if (old_mode & (S_ISUID | S_ISGID | S_ISVTX) + && BITS_EQUAL(old_mode, mode, CHMOD_BITS)) + old_mode &= ~(S_ISUID | S_ISGID | S_ISVTX); +#endif + + /* Return the mode of the file on disk, as we will set them. */ + return (old_mode & ~ACCESSPERMS) | (mode & ACCESSPERMS); +} +#endif + +static int set_rsync_acl(const char *fname, acl_duo *duo_item, + SMB_ACL_TYPE_T type, stat_x *sxp, mode_t mode) +{ + if (type == SMB_ACL_TYPE_DEFAULT + && duo_item->racl.user_obj == NO_ENTRY) { + int rc; +#ifdef SUPPORT_XATTRS + /* --fake-super support: delete default ACL from xattrs. */ + if (am_root < 0) + rc = del_def_xattr_acl(fname); + else +#endif + rc = sys_acl_delete_def_file(fname); + if (rc < 0) { + rsyserr(FERROR_XFER, errno, "set_acl: sys_acl_delete_def_file(%s)", + fname); + return -1; + } +#ifdef SUPPORT_XATTRS + } else if (am_root < 0) { + /* --fake-super support: store ACLs in an xattr. */ + int cnt = duo_item->racl.names.count; + size_t len = 4*4 + cnt * (4+4); + char *buf = new_array(char, len); + int rc; + + SIVAL(buf, 0, duo_item->racl.user_obj); + SIVAL(buf, 4, duo_item->racl.group_obj); + SIVAL(buf, 8, duo_item->racl.mask_obj); + SIVAL(buf, 12, duo_item->racl.other_obj); + + if (cnt) { + char *bp = buf + 4*4; + id_access *ida = duo_item->racl.names.idas; + for ( ; cnt--; ida++, bp += 4+4) { + SIVAL(bp, 0, ida->id); + SIVAL(bp, 4, ida->access); + } + } + rc = set_xattr_acl(fname, type == SMB_ACL_TYPE_ACCESS, buf, len); + free(buf); + return rc; +#endif + } else { + mode_t cur_mode = sxp->st.st_mode; + if (!duo_item->sacl + && !pack_smb_acl(&duo_item->sacl, &duo_item->racl)) + return -1; +#ifdef HAVE_OSX_ACLS + mode = 0; /* eliminate compiler warning */ +#else + if (type == SMB_ACL_TYPE_ACCESS) { + cur_mode = change_sacl_perms(duo_item->sacl, &duo_item->racl, + cur_mode, mode); + if (cur_mode == (mode_t)-1) + return 0; + } +#endif + if (sys_acl_set_file(fname, type, duo_item->sacl) < 0) { + rsyserr(FERROR_XFER, errno, "set_acl: sys_acl_set_file(%s, %s)", + fname, str_acl_type(type)); + return -1; + } + if (type == SMB_ACL_TYPE_ACCESS) + sxp->st.st_mode = cur_mode; + } + + return 0; +} + +/* Given a fname, this sets extended access ACL entries, the default ACL (for a + * dir), and the regular mode bits on the file. Call this with fname set to + * NULL to just check if the ACL is different. + * + * If the ACL operation has a side-effect of changing the file's mode, the + * sxp->st.st_mode value will be changed to match. + * + * Returns 0 for an unchanged ACL, 1 for changed, -1 for failed. */ +int set_acl(const char *fname, const struct file_struct *file, stat_x *sxp, mode_t new_mode) +{ + int changed = 0; + int32 ndx; + BOOL eq; + + if (!dry_run && (read_only || list_only)) { + errno = EROFS; + return -1; + } + + ndx = F_ACL(file); + if (ndx >= 0 && (size_t)ndx < access_acl_list.count) { + acl_duo *duo_item = access_acl_list.items; + duo_item += ndx; + eq = sxp->acc_acl + && rsync_acl_equal_enough(sxp->acc_acl, &duo_item->racl, new_mode); + if (!eq) { + changed = 1; + if (!dry_run && fname + && set_rsync_acl(fname, duo_item, SMB_ACL_TYPE_ACCESS, + sxp, new_mode) < 0) + return -1; + } + } + + if (!S_ISDIR(new_mode)) + return changed; + + ndx = F_DIR_DEFACL(file); + if (ndx >= 0 && (size_t)ndx < default_acl_list.count) { + acl_duo *duo_item = default_acl_list.items; + duo_item += ndx; + eq = sxp->def_acl && rsync_acl_equal(sxp->def_acl, &duo_item->racl); + if (!eq) { + changed = 1; + if (!dry_run && fname + && set_rsync_acl(fname, duo_item, SMB_ACL_TYPE_DEFAULT, + sxp, new_mode) < 0) + return -1; + } + } + + return changed; +} + +/* Non-incremental recursion needs to convert all the received IDs. + * This is done in a single pass after receiving the whole file-list. */ +static void match_racl_ids(const item_list *racl_list) +{ + int list_cnt, name_cnt; + acl_duo *duo_item = racl_list->items; + for (list_cnt = racl_list->count; list_cnt--; duo_item++) { + ida_entries *idal = &duo_item->racl.names; + id_access *ida = idal->idas; + for (name_cnt = idal->count; name_cnt--; ida++) { + if (ida->access & NAME_IS_USER) + ida->id = match_uid(ida->id); + else + ida->id = match_gid(ida->id, NULL); + } + } +} + +void match_acl_ids(void) +{ + match_racl_ids(&access_acl_list); + match_racl_ids(&default_acl_list); +} + +/* This is used by dest_mode(). */ +int default_perms_for_dir(const char *dir) +{ + rsync_acl racl; + SMB_ACL_T sacl; + BOOL ok; + int perms; + + if (dir == NULL) + dir = "."; + perms = ACCESSPERMS & ~orig_umask; + /* Read the directory's default ACL. If it has none, this will successfully return an empty ACL. */ + sacl = sys_acl_get_file(dir, SMB_ACL_TYPE_DEFAULT); + if (sacl == NULL) { + /* Couldn't get an ACL. Darn. */ + switch (errno) { + case EINVAL: + /* If SMB_ACL_TYPE_DEFAULT isn't valid, then the ACLs must be non-POSIX. */ + break; +#ifdef ENOTSUP + case ENOTSUP: +#endif + case ENOSYS: + /* No ACLs are available. */ + break; + case ENOENT: + if (dry_run) { + /* We're doing a dry run, so the containing directory + * wasn't actually created. Don't worry about it. */ + break; + } + /* Otherwise fall through. */ + default: + rprintf(FWARNING, + "default_perms_for_dir: sys_acl_get_file(%s, %s): %s, falling back on umask\n", + dir, str_acl_type(SMB_ACL_TYPE_DEFAULT), strerror(errno)); + } + return perms; + } + + /* Convert it. */ + racl = empty_rsync_acl; + ok = unpack_smb_acl(sacl, &racl); + sys_acl_free_acl(sacl); + if (!ok) { + rprintf(FWARNING, "default_perms_for_dir: unpack_smb_acl failed, falling back on umask\n"); + return perms; + } + + /* Apply the permission-bit entries of the default ACL, if any. */ + if (racl.user_obj != NO_ENTRY) { + perms = rsync_acl_get_perms(&racl); + if (DEBUG_GTE(ACL, 1)) + rprintf(FINFO, "got ACL-based default perms %o for directory %s\n", perms, dir); + } + + rsync_acl_free(&racl); + return perms; +} + +#endif /* SUPPORT_ACLS */ diff --git a/rsync/authenticate.c b/rsync/authenticate.c new file mode 100644 index 0000000..c92746c --- /dev/null +++ b/rsync/authenticate.c @@ -0,0 +1,373 @@ +/* + * Support rsync daemon authentication. + * + * Copyright (C) 1998-2000 Andrew Tridgell + * Copyright (C) 2002-2014 Wayne Davison + * + * 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, visit the http://fsf.org website. + */ + +#include "rsync.h" +#include "itypes.h" + +extern int read_only; +extern char *password_file; + +/*************************************************************************** +encode a buffer using base64 - simple and slow algorithm. null terminates +the result. + ***************************************************************************/ +void base64_encode(const char *buf, int len, char *out, int pad) +{ + char *b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + int bit_offset, byte_offset, idx, i; + const uchar *d = (const uchar *)buf; + int bytes = (len*8 + 5)/6; + + for (i = 0; i < bytes; i++) { + byte_offset = (i*6)/8; + bit_offset = (i*6)%8; + if (bit_offset < 3) { + idx = (d[byte_offset] >> (2-bit_offset)) & 0x3F; + } else { + idx = (d[byte_offset] << (bit_offset-2)) & 0x3F; + if (byte_offset+1 < len) { + idx |= (d[byte_offset+1] >> (8-(bit_offset-2))); + } + } + out[i] = b64[idx]; + } + + while (pad && (i % 4)) + out[i++] = '='; + + out[i] = '\0'; +} + +/* Generate a challenge buffer and return it base64-encoded. */ +static void gen_challenge(const char *addr, char *challenge) +{ + char input[32]; + char digest[MAX_DIGEST_LEN]; + struct timeval tv; + int len; + + memset(input, 0, sizeof input); + + strlcpy(input, addr, 17); + sys_gettimeofday(&tv); + SIVAL(input, 16, tv.tv_sec); + SIVAL(input, 20, tv.tv_usec); + SIVAL(input, 24, getpid()); + + sum_init(0); + sum_update(input, sizeof input); + len = sum_end(digest); + + base64_encode(digest, len, challenge, 0); +} + +/* Generate an MD4 hash created from the combination of the password + * and the challenge string and return it base64-encoded. */ +static void generate_hash(const char *in, const char *challenge, char *out) +{ + char buf[MAX_DIGEST_LEN]; + int len; + + sum_init(0); + sum_update(in, strlen(in)); + sum_update(challenge, strlen(challenge)); + len = sum_end(buf); + + base64_encode(buf, len, out, 0); +} + +/* Return the secret for a user from the secret file, null terminated. + * Maximum length is len (not counting the null). */ +static const char *check_secret(int module, const char *user, const char *group, + const char *challenge, const char *pass) +{ + char line[1024]; + char pass2[MAX_DIGEST_LEN*2]; + const char *fname = lp_secrets_file(module); + STRUCT_STAT st; + int ok = 1; + int user_len = strlen(user); + int group_len = group ? strlen(group) : 0; + char *err; + FILE *fh; + + if (!fname || !*fname || (fh = fopen(fname, "r")) == NULL) + return "no secrets file"; + + if (do_fstat(fileno(fh), &st) == -1) { + rsyserr(FLOG, errno, "fstat(%s)", fname); + ok = 0; + } else if (lp_strict_modes(module)) { + if ((st.st_mode & 06) != 0) { + rprintf(FLOG, "secrets file must not be other-accessible (see strict modes option)\n"); + ok = 0; + } else if (MY_UID() == 0 && st.st_uid != 0) { + rprintf(FLOG, "secrets file must be owned by root when running as root (see strict modes)\n"); + ok = 0; + } + } + if (!ok) { + fclose(fh); + return "ignoring secrets file"; + } + + if (*user == '#') { + /* Reject attempt to match a comment. */ + fclose(fh); + return "invalid username"; + } + + /* Try to find a line that starts with the user (or @group) name and a ':'. */ + err = "secret not found"; + while ((user || group) && fgets(line, sizeof line, fh) != NULL) { + const char **ptr, *s = strtok(line, "\n\r"); + int len; + if (!s) + continue; + if (*s == '@') { + ptr = &group; + len = group_len; + s++; + } else { + ptr = &user; + len = user_len; + } + if (!*ptr || strncmp(s, *ptr, len) != 0 || s[len] != ':') + continue; + generate_hash(s+len+1, challenge, pass2); + if (strcmp(pass, pass2) == 0) { + err = NULL; + break; + } + err = "password mismatch"; + *ptr = NULL; /* Don't look for name again. */ + } + + fclose(fh); + + memset(line, 0, sizeof line); + memset(pass2, 0, sizeof pass2); + + return err; +} + +static const char *getpassf(const char *filename) +{ + STRUCT_STAT st; + char buffer[512], *p; + int n; + + if (!filename) + return NULL; + + if (strcmp(filename, "-") == 0) { + n = fgets(buffer, sizeof buffer, stdin) == NULL ? -1 : (int)strlen(buffer); + } else { + int fd; + + if ((fd = open(filename,O_RDONLY)) < 0) { + rsyserr(FERROR, errno, "could not open password file %s", filename); + exit_cleanup(RERR_SYNTAX); + } + + if (do_stat(filename, &st) == -1) { + rsyserr(FERROR, errno, "stat(%s)", filename); + exit_cleanup(RERR_SYNTAX); + } + if ((st.st_mode & 06) != 0) { + rprintf(FERROR, "ERROR: password file must not be other-accessible\n"); + exit_cleanup(RERR_SYNTAX); + } + if (MY_UID() == 0 && st.st_uid != 0) { + rprintf(FERROR, "ERROR: password file must be owned by root when running as root\n"); + exit_cleanup(RERR_SYNTAX); + } + + n = read(fd, buffer, sizeof buffer - 1); + close(fd); + } + + if (n > 0) { + buffer[n] = '\0'; + if ((p = strtok(buffer, "\n\r")) != NULL) + return strdup(p); + } + + rprintf(FERROR, "ERROR: failed to read a password from %s\n", filename); + exit_cleanup(RERR_SYNTAX); +} + +/* Possibly negotiate authentication with the client. Use "leader" to + * start off the auth if necessary. + * + * Return NULL if authentication failed. Return "" if anonymous access. + * Otherwise return username. + */ +char *auth_server(int f_in, int f_out, int module, const char *host, + const char *addr, const char *leader) +{ + char *users = lp_auth_users(module); + char challenge[MAX_DIGEST_LEN*2]; + char line[BIGPATHBUFLEN]; + char **auth_uid_groups = NULL; + int auth_uid_groups_cnt = -1; + const char *err = NULL; + int group_match = -1; + char *tok, *pass; + char opt_ch = '\0'; + + /* if no auth list then allow anyone in! */ + if (!users || !*users) + return ""; + + gen_challenge(addr, challenge); + + io_printf(f_out, "%s%s\n", leader, challenge); + + if (!read_line_old(f_in, line, sizeof line, 0) + || (pass = strchr(line, ' ')) == NULL) { + rprintf(FLOG, "auth failed on module %s from %s (%s): " + "invalid challenge response\n", + lp_name(module), host, addr); + return NULL; + } + *pass++ = '\0'; + + if (!(users = strdup(users))) + out_of_memory("auth_server"); + + for (tok = strtok(users, " ,\t"); tok; tok = strtok(NULL, " ,\t")) { + char *opts; + /* See if the user appended :deny, :ro, or :rw. */ + if ((opts = strchr(tok, ':')) != NULL) { + *opts++ = '\0'; + opt_ch = isUpper(opts) ? toLower(opts) : *opts; + if (opt_ch == 'r') { /* handle ro and rw */ + opt_ch = isUpper(opts+1) ? toLower(opts+1) : opts[1]; + if (opt_ch == 'o') + opt_ch = 'r'; + else if (opt_ch != 'w') + opt_ch = '\0'; + } else if (opt_ch != 'd') /* if it's not deny, ignore it */ + opt_ch = '\0'; + } else + opt_ch = '\0'; + if (*tok != '@') { + /* Match the username */ + if (wildmatch(tok, line)) + break; + } else { +#ifdef HAVE_GETGROUPLIST + int j; + /* See if authorizing user is a real user, and if so, see + * if it is in a group that matches tok+1 wildmat. */ + if (auth_uid_groups_cnt < 0) { + gid_t gid_list[64]; + uid_t auth_uid; + auth_uid_groups_cnt = sizeof gid_list / sizeof (gid_t); + if (!user_to_uid(line, &auth_uid, False) + || getallgroups(auth_uid, gid_list, &auth_uid_groups_cnt) != NULL) + auth_uid_groups_cnt = 0; + else { + if ((auth_uid_groups = new_array(char *, auth_uid_groups_cnt)) == NULL) + out_of_memory("auth_server"); + for (j = 0; j < auth_uid_groups_cnt; j++) + auth_uid_groups[j] = gid_to_group(gid_list[j]); + } + } + for (j = 0; j < auth_uid_groups_cnt; j++) { + if (auth_uid_groups[j] && wildmatch(tok+1, auth_uid_groups[j])) { + group_match = j; + break; + } + } + if (group_match >= 0) + break; +#else + rprintf(FLOG, "your computer doesn't support getgrouplist(), so no @group authorization is possible.\n"); +#endif + } + } + + free(users); + + if (!tok) + err = "no matching rule"; + else if (opt_ch == 'd') + err = "denied by rule"; + else { + char *group = group_match >= 0 ? auth_uid_groups[group_match] : NULL; + err = check_secret(module, line, group, challenge, pass); + } + + memset(challenge, 0, sizeof challenge); + memset(pass, 0, strlen(pass)); + + if (auth_uid_groups) { + int j; + for (j = 0; j < auth_uid_groups_cnt; j++) { + if (auth_uid_groups[j]) + free(auth_uid_groups[j]); + } + free(auth_uid_groups); + } + + if (err) { + rprintf(FLOG, "auth failed on module %s from %s (%s) for %s: %s\n", + lp_name(module), host, addr, line, err); + return NULL; + } + + if (opt_ch == 'r') + read_only = 1; + else if (opt_ch == 'w') + read_only = 0; + + return strdup(line); +} + +void auth_client(int fd, const char *user, const char *challenge) +{ + const char *pass; + char pass2[MAX_DIGEST_LEN*2]; + + if (!user || !*user) + user = "nobody"; + + if (!(pass = getpassf(password_file)) + && !(pass = getenv("RSYNC_PASSWORD"))) { + /* XXX: cyeoh says that getpass is deprecated, because + * it may return a truncated password on some systems, + * and it is not in the LSB. + * + * Andrew Klein says that getpassphrase() is present + * on Solaris and reads up to 256 characters. + * + * OpenBSD has a readpassphrase() that might be more suitable. + */ + pass = getpass("Password: "); + } + + if (!pass) + pass = ""; + + generate_hash(pass, challenge, pass2); + io_printf(fd, "%s %s\n", user, pass2); +} diff --git a/rsync/backup.c b/rsync/backup.c new file mode 100644 index 0000000..8987723 --- /dev/null +++ b/rsync/backup.c @@ -0,0 +1,341 @@ +/* + * Backup handling code. + * + * Copyright (C) 1999 Andrew Tridgell + * Copyright (C) 2003-2014 Wayne Davison + * + * 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, visit the http://fsf.org website. + */ + +#include "rsync.h" +#include "ifuncs.h" + +extern int am_root; +extern int preserve_acls; +extern int preserve_xattrs; +extern int preserve_devices; +extern int preserve_specials; +extern int preserve_links; +extern int safe_symlinks; +extern int backup_dir_len; +extern unsigned int backup_dir_remainder; +extern char backup_dir_buf[MAXPATHLEN]; +extern char *backup_suffix; +extern char *backup_dir; + +/* Returns -1 on error, 0 on missing dir, and 1 on present dir. */ +static int validate_backup_dir(void) +{ + STRUCT_STAT st; + + if (do_lstat(backup_dir_buf, &st) < 0) { + if (errno == ENOENT) + return 0; + rsyserr(FERROR, errno, "backup lstat %s failed", backup_dir_buf); + return -1; + } + if (!S_ISDIR(st.st_mode)) { + int flags = get_del_for_flag(st.st_mode) | DEL_FOR_BACKUP | DEL_RECURSE; + if (delete_item(backup_dir_buf, st.st_mode, flags) == 0) + return 0; + return -1; + } + return 1; +} + +/* Create a backup path from the given fname, putting the result into + * backup_dir_buf. Any new directories (compared to the prior backup + * path) are ensured to exist as directories, replacing anything else + * that may be in the way (e.g. a symlink). */ +static BOOL copy_valid_path(const char *fname) +{ + const char *f; + int val; + BOOL ret = True; + stat_x sx; + char *b, *rel = backup_dir_buf + backup_dir_len, *name = rel; + + for (f = fname, b = rel; *f && *f == *b; f++, b++) { + if (*b == '/') + name = b + 1; + } + + if (stringjoin(rel, backup_dir_remainder, fname, backup_suffix, NULL) >= backup_dir_remainder) { + rprintf(FERROR, "backup filename too long\n"); + *name = '\0'; + return False; + } + + for ( ; ; name = b + 1) { + if ((b = strchr(name, '/')) == NULL) + return True; + *b = '\0'; + + val = validate_backup_dir(); + if (val == 0) + break; + if (val < 0) { + *name = '\0'; + return False; + } + + *b = '/'; + } + + init_stat_x(&sx); + + for ( ; b; name = b + 1, b = strchr(name, '/')) { + *b = '\0'; + + while (do_mkdir(backup_dir_buf, ACCESSPERMS) < 0) { + if (errno == EEXIST) { + val = validate_backup_dir(); + if (val > 0) + break; + if (val == 0) + continue; + } else + rsyserr(FERROR, errno, "backup mkdir %s failed", backup_dir_buf); + *name = '\0'; + ret = False; + goto cleanup; + } + + /* Try to transfer the directory settings of the actual dir + * that the files are coming from. */ + if (x_stat(rel, &sx.st, NULL) < 0) + rsyserr(FERROR, errno, "backup stat %s failed", full_fname(rel)); + else { + struct file_struct *file; + if (!(file = make_file(rel, NULL, NULL, 0, NO_FILTERS))) + continue; +#ifdef SUPPORT_ACLS + if (preserve_acls && !S_ISLNK(file->mode)) { + get_acl(rel, &sx); + cache_tmp_acl(file, &sx); + free_acl(&sx); + } +#endif +#ifdef SUPPORT_XATTRS + if (preserve_xattrs) { + get_xattr(rel, &sx); + cache_tmp_xattr(file, &sx); + free_xattr(&sx); + } +#endif + set_file_attrs(backup_dir_buf, file, NULL, NULL, 0); + unmake_file(file); + } + + *b = '/'; + } + + cleanup: + +#ifdef SUPPORT_ACLS + uncache_tmp_acls(); +#endif +#ifdef SUPPORT_XATTRS + uncache_tmp_xattrs(); +#endif + + return ret; +} + +/* Make a complete pathname for backup file and verify any new path elements. */ +char *get_backup_name(const char *fname) +{ + if (backup_dir) { + /* copy fname into backup_dir_buf while validating the dirs. */ + if (copy_valid_path(fname)) + return backup_dir_buf; + /* copy_valid_path() has printed an error message. */ + return NULL; + } + + if (stringjoin(backup_dir_buf, MAXPATHLEN, fname, backup_suffix, NULL) < MAXPATHLEN) + return backup_dir_buf; + + rprintf(FERROR, "backup filename too long\n"); + return NULL; +} + +/* Has same return codes as make_backup(). */ +static inline int link_or_rename(const char *from, const char *to, + BOOL prefer_rename, STRUCT_STAT *stp) +{ +#ifdef SUPPORT_HARD_LINKS + if (!prefer_rename) { +#ifndef CAN_HARDLINK_SYMLINK + if (S_ISLNK(stp->st_mode)) + return 0; /* Use copy code. */ +#endif +#ifndef CAN_HARDLINK_SPECIAL + if (IS_SPECIAL(stp->st_mode) || IS_DEVICE(stp->st_mode)) + return 0; /* Use copy code. */ +#endif + if (do_link(from, to) == 0) { + if (DEBUG_GTE(BACKUP, 1)) + rprintf(FINFO, "make_backup: HLINK %s successful.\n", from); + return 2; + } + /* We prefer to rename a regular file rather than copy it. */ + if (!S_ISREG(stp->st_mode) || errno == EEXIST || errno == EISDIR) + return 0; + } +#endif + if (do_rename(from, to) == 0) { + if (stp->st_nlink > 1 && !S_ISDIR(stp->st_mode)) { + /* If someone has hard-linked the file into the backup + * dir, rename() might return success but do nothing! */ + robust_unlink(from); /* Just in case... */ + } + if (DEBUG_GTE(BACKUP, 1)) + rprintf(FINFO, "make_backup: RENAME %s successful.\n", from); + return 1; + } + return 0; +} + +/* Hard-link, rename, or copy an item to the backup name. Returns 2 if item + * was duplicated into backup area, 1 if item was moved, or 0 for failure.*/ +int make_backup(const char *fname, BOOL prefer_rename) +{ + stat_x sx; + struct file_struct *file; + int save_preserve_xattrs; + char *buf = get_backup_name(fname); + int ret = 0; + + if (!buf) + return 0; + + init_stat_x(&sx); + /* Return success if no file to keep. */ + if (x_lstat(fname, &sx.st, NULL) < 0) + return 1; + + /* Try a hard-link or a rename first. Using rename is not atomic, but + * is more efficient than forcing a copy for larger files when no hard- + * linking is possible. */ + if ((ret = link_or_rename(fname, buf, prefer_rename, &sx.st)) != 0) + goto success; + if (errno == EEXIST || errno == EISDIR) { + STRUCT_STAT bakst; + if (do_lstat(buf, &bakst) == 0) { + int flags = get_del_for_flag(bakst.st_mode) | DEL_FOR_BACKUP | DEL_RECURSE; + if (delete_item(buf, bakst.st_mode, flags) != 0) + return 0; + } + if ((ret = link_or_rename(fname, buf, prefer_rename, &sx.st)) != 0) + goto success; + } + + /* Fall back to making a copy. */ + if (!(file = make_file(fname, NULL, &sx.st, 0, NO_FILTERS))) + return 1; /* the file could have disappeared */ + +#ifdef SUPPORT_ACLS + if (preserve_acls && !S_ISLNK(file->mode)) { + get_acl(fname, &sx); + cache_tmp_acl(file, &sx); + free_acl(&sx); + } +#endif +#ifdef SUPPORT_XATTRS + if (preserve_xattrs) { + get_xattr(fname, &sx); + cache_tmp_xattr(file, &sx); + free_xattr(&sx); + } +#endif + + /* Check to see if this is a device file, or link */ + if ((am_root && preserve_devices && IS_DEVICE(file->mode)) + || (preserve_specials && IS_SPECIAL(file->mode))) { + if (do_mknod(buf, file->mode, sx.st.st_rdev) < 0) + rsyserr(FERROR, errno, "mknod %s failed", full_fname(buf)); + else if (DEBUG_GTE(BACKUP, 1)) + rprintf(FINFO, "make_backup: DEVICE %s successful.\n", fname); + ret = 2; + } + +#ifdef SUPPORT_LINKS + if (!ret && preserve_links && S_ISLNK(file->mode)) { + const char *sl = F_SYMLINK(file); + if (safe_symlinks && unsafe_symlink(sl, fname)) { + if (INFO_GTE(SYMSAFE, 1)) { + rprintf(FINFO, "not backing up unsafe symlink \"%s\" -> \"%s\"\n", + fname, sl); + } + ret = 2; + } else { + if (do_symlink(sl, buf) < 0) + rsyserr(FERROR, errno, "link %s -> \"%s\"", full_fname(buf), sl); + else if (DEBUG_GTE(BACKUP, 1)) + rprintf(FINFO, "make_backup: SYMLINK %s successful.\n", fname); + ret = 2; + } + } +#endif + + if (!ret && !S_ISREG(file->mode)) { + rprintf(FINFO, "make_bak: skipping non-regular file %s\n", fname); + unmake_file(file); +#ifdef SUPPORT_ACLS + uncache_tmp_acls(); +#endif +#ifdef SUPPORT_XATTRS + uncache_tmp_xattrs(); +#endif + return 2; + } + + /* Copy to backup tree if a file. */ + if (!ret) { + if (copy_file(fname, buf, -1, file->mode) < 0) { + rsyserr(FERROR, errno, "keep_backup failed: %s -> \"%s\"", + full_fname(fname), buf); + unmake_file(file); +#ifdef SUPPORT_ACLS + uncache_tmp_acls(); +#endif +#ifdef SUPPORT_XATTRS + uncache_tmp_xattrs(); +#endif + return 0; + } + if (DEBUG_GTE(BACKUP, 1)) + rprintf(FINFO, "make_backup: COPY %s successful.\n", fname); + ret = 2; + } + + save_preserve_xattrs = preserve_xattrs; + preserve_xattrs = 0; + set_file_attrs(buf, file, NULL, fname, 0); + preserve_xattrs = save_preserve_xattrs; + + unmake_file(file); +#ifdef SUPPORT_ACLS + uncache_tmp_acls(); +#endif +#ifdef SUPPORT_XATTRS + uncache_tmp_xattrs(); +#endif + + success: + if (INFO_GTE(BACKUP, 1)) + rprintf(FINFO, "backed up %s to %s\n", fname, buf); + return ret; +} diff --git a/rsync/batch.c b/rsync/batch.c new file mode 100644 index 0000000..5b329ba --- /dev/null +++ b/rsync/batch.c @@ -0,0 +1,283 @@ +/* + * Support for the batch-file options. + * + * Copyright (C) 1999 Weiss + * Copyright (C) 2004 Chris Shoemaker + * Copyright (C) 2004-2014 Wayne Davison + * + * 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, visit the http://fsf.org website. + */ + +#include "rsync.h" +#include +#include + +extern int eol_nulls; +extern int recurse; +extern int xfer_dirs; +extern int preserve_links; +extern int preserve_hard_links; +extern int preserve_devices; +extern int preserve_uid; +extern int preserve_gid; +extern int preserve_acls; +extern int preserve_xattrs; +extern int always_checksum; +extern int do_compression; +extern int inplace; +extern int append_mode; +extern int protocol_version; +extern char *batch_name; +#ifdef ICONV_OPTION +extern char *iconv_opt; +#endif + +extern filter_rule_list filter_list; + +int batch_stream_flags; + +static int tweaked_append; +static int tweaked_append_verify; +static int tweaked_iconv; + +static int *flag_ptr[] = { + &recurse, /* 0 */ + &preserve_uid, /* 1 */ + &preserve_gid, /* 2 */ + &preserve_links, /* 3 */ + &preserve_devices, /* 4 */ + &preserve_hard_links, /* 5 */ + &always_checksum, /* 6 */ + &xfer_dirs, /* 7 (protocol 29) */ + &do_compression, /* 8 (protocol 29) */ + &tweaked_iconv, /* 9 (protocol 30) */ + &preserve_acls, /* 10 (protocol 30) */ + &preserve_xattrs, /* 11 (protocol 30) */ + &inplace, /* 12 (protocol 30) */ + &tweaked_append, /* 13 (protocol 30) */ + &tweaked_append_verify, /* 14 (protocol 30) */ + NULL +}; + +static char *flag_name[] = { + "--recurse (-r)", + "--owner (-o)", + "--group (-g)", + "--links (-l)", + "--devices (-D)", + "--hard-links (-H)", + "--checksum (-c)", + "--dirs (-d)", + "--compress (-z)", + "--iconv", + "--acls (-A)", + "--xattrs (-X)", + "--inplace", + "--append", + "--append-verify", + NULL +}; + +void write_stream_flags(int fd) +{ + int i, flags; + + tweaked_append = append_mode == 1; + tweaked_append_verify = append_mode == 2; +#ifdef ICONV_OPTION + tweaked_iconv = iconv_opt != NULL; +#endif + + /* Start the batch file with a bitmap of data-stream-affecting + * flags. */ + for (i = 0, flags = 0; flag_ptr[i]; i++) { + if (*flag_ptr[i]) + flags |= 1 << i; + } + write_int(fd, flags); +} + +void read_stream_flags(int fd) +{ + batch_stream_flags = read_int(fd); +} + +void check_batch_flags(void) +{ + int i; + + if (protocol_version < 29) + flag_ptr[7] = NULL; + else if (protocol_version < 30) + flag_ptr[9] = NULL; + tweaked_append = append_mode == 1; + tweaked_append_verify = append_mode == 2; +#ifdef ICONV_OPTION + tweaked_iconv = iconv_opt != NULL; +#endif + for (i = 0; flag_ptr[i]; i++) { + int set = batch_stream_flags & (1 << i) ? 1 : 0; + if (*flag_ptr[i] != set) { + if (i == 9) { + rprintf(FERROR, + "%s specify the --iconv option to use this batch file.\n", + set ? "Please" : "Do not"); + exit_cleanup(RERR_SYNTAX); + } + if (INFO_GTE(MISC, 1)) { + rprintf(FINFO, + "%sing the %s option to match the batchfile.\n", + set ? "Sett" : "Clear", flag_name[i]); + } + *flag_ptr[i] = set; + } + } + if (protocol_version < 29) { + if (recurse) + xfer_dirs |= 1; + else if (xfer_dirs < 2) + xfer_dirs = 0; + } + + if (tweaked_append) + append_mode = 1; + else if (tweaked_append_verify) + append_mode = 2; +} + +static int write_arg(int fd, char *arg) +{ + char *x, *s; + int len, ret = 0; + + if (*arg == '-' && (x = strchr(arg, '=')) != NULL) { + if (write(fd, arg, x - arg + 1) != x - arg + 1) + ret = -1; + arg += x - arg + 1; + } + + if (strpbrk(arg, " \"'&;|[]()$#!*?^\\") != NULL) { + if (write(fd, "'", 1) != 1) + ret = -1; + for (s = arg; (x = strchr(s, '\'')) != NULL; s = x + 1) { + if (write(fd, s, x - s + 1) != x - s + 1 + || write(fd, "'", 1) != 1) + ret = -1; + } + len = strlen(s); + if (write(fd, s, len) != len + || write(fd, "'", 1) != 1) + ret = -1; + return ret; + } + + len = strlen(arg); + if (write(fd, arg, len) != len) + ret = -1; + + return ret; +} + +static void write_filter_rules(int fd) +{ + filter_rule *ent; + + write_sbuf(fd, " <<'#E#'\n"); + for (ent = filter_list.head; ent; ent = ent->next) { + unsigned int plen; + char *p = get_rule_prefix(ent, "- ", 0, &plen); + write_buf(fd, p, plen); + write_sbuf(fd, ent->pattern); + if (ent->rflags & FILTRULE_DIRECTORY) + write_byte(fd, '/'); + write_byte(fd, eol_nulls ? 0 : '\n'); + } + if (eol_nulls) + write_sbuf(fd, ";\n"); + write_sbuf(fd, "#E#"); +} + +/* This routine tries to write out an equivalent --read-batch command + * given the user's --write-batch args. However, it doesn't really + * understand most of the options, so it uses some overly simple + * heuristics to munge the command line into something that will + * (hopefully) work. */ +void write_batch_shell_file(int argc, char *argv[], int file_arg_cnt) +{ + int fd, i, len, err = 0; + char *p, filename[MAXPATHLEN]; + + stringjoin(filename, sizeof filename, + batch_name, ".sh", NULL); + fd = do_open(filename, O_WRONLY | O_CREAT | O_TRUNC, + S_IRUSR | S_IWUSR | S_IXUSR); + if (fd < 0) { + rsyserr(FERROR, errno, "Batch file %s open error", + filename); + exit_cleanup(RERR_FILESELECT); + } + + /* Write argvs info to BATCH.sh file */ + if (write_arg(fd, argv[0]) < 0) + err = 1; + if (filter_list.head) { + if (protocol_version >= 29) + write_sbuf(fd, " --filter=._-"); + else + write_sbuf(fd, " --exclude-from=-"); + } + for (i = 1; i < argc - file_arg_cnt; i++) { + p = argv[i]; + if (strncmp(p, "--files-from", 12) == 0 + || strncmp(p, "--filter", 8) == 0 + || strncmp(p, "--include", 9) == 0 + || strncmp(p, "--exclude", 9) == 0) { + if (strchr(p, '=') == NULL) + i++; + continue; + } + if (strcmp(p, "-f") == 0) { + i++; + continue; + } + if (write(fd, " ", 1) != 1) + err = 1; + if (strncmp(p, "--write-batch", len = 13) == 0 + || strncmp(p, "--only-write-batch", len = 18) == 0) { + if (write(fd, "--read-batch", 12) != 12) + err = 1; + if (p[len] == '=') { + if (write(fd, "=", 1) != 1 + || write_arg(fd, p + len + 1) < 0) + err = 1; + } + } else { + if (write_arg(fd, p) < 0) + err = 1; + } + } + if (!(p = check_for_hostspec(argv[argc - 1], &p, &i))) + p = argv[argc - 1]; + if (write(fd, " ${1:-", 6) != 6 + || write_arg(fd, p) < 0) + err = 1; + write_byte(fd, '}'); + if (filter_list.head) + write_filter_rules(fd); + if (write(fd, "\n", 1) != 1 || close(fd) < 0 || err) { + rsyserr(FERROR, errno, "Batch file %s write error", + filename); + exit_cleanup(RERR_FILEIO); + } +} diff --git a/rsync/byteorder.h b/rsync/byteorder.h new file mode 100644 index 0000000..49c358b --- /dev/null +++ b/rsync/byteorder.h @@ -0,0 +1,124 @@ +/* + * Simple byteorder handling. + * + * Copyright (C) 1992-1995 Andrew Tridgell + * Copyright (C) 2007-2014 Wayne Davison + * + * 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, visit the http://fsf.org website. + */ + +#undef CAREFUL_ALIGNMENT +#undef AVOID_BYTEORDER_INLINE + +/* We know that the x86 can handle misalignment and has the same + * byte order (LSB-first) as the 32-bit numbers we transmit. */ +#if defined __i386__ || defined __i486__ || defined __i586__ || defined __i686__ || __amd64 +#define CAREFUL_ALIGNMENT 0 +#endif + +#ifndef CAREFUL_ALIGNMENT +#define CAREFUL_ALIGNMENT 1 +#endif + +#define CVAL(buf,pos) (((unsigned char *)(buf))[pos]) +#define UVAL(buf,pos) ((uint32)CVAL(buf,pos)) + +#if CAREFUL_ALIGNMENT + +#define PVAL(buf,pos) (UVAL(buf,pos)|UVAL(buf,(pos)+1)<<8) +#define IVAL(buf,pos) (PVAL(buf,pos)|PVAL(buf,(pos)+2)<<16) +#define IVAL64(buf,pos) (IVAL(buf,pos)|(int64)IVAL(buf,(pos)+4)<<32) +#define SSVALX(buf,pos,val) (CVAL(buf,pos)=(val)&0xFF,CVAL(buf,pos+1)=(val)>>8) +#define SIVALX(buf,pos,val) (SSVALX(buf,pos,val&0xFFFF),SSVALX(buf,pos+2,val>>16)) +#define SIVAL(buf,pos,val) SIVALX(buf,pos,(uint32)(val)) +#define SIVAL64(buf,pos,val) (SIVAL(buf,pos,val),SIVAL(buf,(pos)+4,(val)>>32)) + +#define IVALu(buf,pos) IVAL(buf,pos) +#define SIVALu(buf,pos,val) SIVAL(buf,pos,val) + +#else /* !CAREFUL_ALIGNMENT */ + +/* This handles things for architectures like the 386 that can handle alignment errors. + * WARNING: This section is dependent on the length of an int32 (and thus a uint32) + * being correct (4 bytes)! Set CAREFUL_ALIGNMENT if it is not. */ + +# ifdef AVOID_BYTEORDER_INLINE + +#define IVAL(buf,pos) (*(uint32 *)((char *)(buf) + (pos))) +#define SIVAL(buf,pos,val) IVAL(buf,pos)=((uint32)(val)) + +#define IVALu(buf,pos) IVAL(buf,pos) +#define SIVALu(buf,pos,val) SIVAL(buf,pos,val) + +# else /* !AVOID_BYTEORDER_INLINE */ + +static inline uint32 +IVALu(const uchar *buf, int pos) +{ + union { + const uchar *b; + const uint32 *num; + } u; + u.b = buf + pos; + return *u.num; +} + +static inline void +SIVALu(uchar *buf, int pos, uint32 val) +{ + union { + uchar *b; + uint32 *num; + } u; + u.b = buf + pos; + *u.num = val; +} + +static inline uint32 +IVAL(const char *buf, int pos) +{ + return IVALu((uchar*)buf, pos); +} + +static inline void +SIVAL(char *buf, int pos, uint32 val) +{ + SIVALu((uchar*)buf, pos, val); +} + +static inline int64 +IVAL64(const char *buf, int pos) +{ + union { + const char *b; + const int64 *num; + } u; + u.b = buf + pos; + return *u.num; +} + +static inline void +SIVAL64(char *buf, int pos, int64 val) +{ + union { + char *b; + int64 *num; + } u; + u.b = buf + pos; + *u.num = val; +} + +# endif /* !AVOID_BYTEORDER_INLINE */ + +#endif /* !CAREFUL_ALIGNMENT */ diff --git a/rsync/case_N.h b/rsync/case_N.h new file mode 100644 index 0000000..72451a9 --- /dev/null +++ b/rsync/case_N.h @@ -0,0 +1,76 @@ +/* + * Allow an arbitrary sequence of case labels. + * + * Copyright (C) 2006-2014 Wayne Davison + * + * 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, visit the http://fsf.org website. + */ + +/* This is included multiple times, once for every segement in a switch statement. + * This produces the next "case N:" statement in sequence. */ + +#if !defined CASE_N_STATE_0 +#define CASE_N_STATE_0 + case 0: +#elif !defined CASE_N_STATE_1 +#define CASE_N_STATE_1 + case 1: +#elif !defined CASE_N_STATE_2 +#define CASE_N_STATE_2 + case 2: +#elif !defined CASE_N_STATE_3 +#define CASE_N_STATE_3 + case 3: +#elif !defined CASE_N_STATE_4 +#define CASE_N_STATE_4 + case 4: +#elif !defined CASE_N_STATE_5 +#define CASE_N_STATE_5 + case 5: +#elif !defined CASE_N_STATE_6 +#define CASE_N_STATE_6 + case 6: +#elif !defined CASE_N_STATE_7 +#define CASE_N_STATE_7 + case 7: +#elif !defined CASE_N_STATE_8 +#define CASE_N_STATE_8 + case 8: +#elif !defined CASE_N_STATE_9 +#define CASE_N_STATE_9 + case 9: +#elif !defined CASE_N_STATE_10 +#define CASE_N_STATE_10 + case 10: +#elif !defined CASE_N_STATE_11 +#define CASE_N_STATE_11 + case 11: +#elif !defined CASE_N_STATE_12 +#define CASE_N_STATE_12 + case 12: +#elif !defined CASE_N_STATE_13 +#define CASE_N_STATE_13 + case 13: +#elif !defined CASE_N_STATE_14 +#define CASE_N_STATE_14 + case 14: +#elif !defined CASE_N_STATE_15 +#define CASE_N_STATE_15 + case 15: +#elif !defined CASE_N_STATE_16 +#define CASE_N_STATE_16 + case 16: +#else +#error Need to add more case statements! +#endif diff --git a/rsync/checksum.c b/rsync/checksum.c new file mode 100644 index 0000000..a1c2aa2 --- /dev/null +++ b/rsync/checksum.c @@ -0,0 +1,223 @@ +/* + * Routines to support checksumming of bytes. + * + * Copyright (C) 1996 Andrew Tridgell + * Copyright (C) 1996 Paul Mackerras + * Copyright (C) 2004-2014 Wayne Davison + * + * 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, visit the http://fsf.org website. + */ + +#include "rsync.h" + +extern int checksum_seed; +extern int protocol_version; + +/* + a simple 32 bit checksum that can be upadted from either end + (inspired by Mark Adler's Adler-32 checksum) + */ +uint32 get_checksum1(char *buf1, int32 len) +{ + int32 i; + uint32 s1, s2; + schar *buf = (schar *)buf1; + + s1 = s2 = 0; + for (i = 0; i < (len-4); i+=4) { + s2 += 4*(s1 + buf[i]) + 3*buf[i+1] + 2*buf[i+2] + buf[i+3] + + 10*CHAR_OFFSET; + s1 += (buf[i+0] + buf[i+1] + buf[i+2] + buf[i+3] + 4*CHAR_OFFSET); + } + for (; i < len; i++) { + s1 += (buf[i]+CHAR_OFFSET); s2 += s1; + } + return (s1 & 0xffff) + (s2 << 16); +} + + +void get_checksum2(char *buf, int32 len, char *sum) +{ + md_context m; + + if (protocol_version >= 30) { + uchar seedbuf[4]; + md5_begin(&m); + md5_update(&m, (uchar *)buf, len); + if (checksum_seed) { + SIVALu(seedbuf, 0, checksum_seed); + md5_update(&m, seedbuf, 4); + } + md5_result(&m, (uchar *)sum); + } else { + int32 i; + static char *buf1; + static int32 len1; + + mdfour_begin(&m); + + if (len > len1) { + if (buf1) + free(buf1); + buf1 = new_array(char, len+4); + len1 = len; + if (!buf1) + out_of_memory("get_checksum2"); + } + + memcpy(buf1, buf, len); + if (checksum_seed) { + SIVAL(buf1,len,checksum_seed); + len += 4; + } + + for (i = 0; i + CSUM_CHUNK <= len; i += CSUM_CHUNK) + mdfour_update(&m, (uchar *)(buf1+i), CSUM_CHUNK); + + /* + * Prior to version 27 an incorrect MD4 checksum was computed + * by failing to call mdfour_tail() for block sizes that + * are multiples of 64. This is fixed by calling mdfour_update() + * even when there are no more bytes. + */ + if (len - i > 0 || protocol_version >= 27) + mdfour_update(&m, (uchar *)(buf1+i), len-i); + + mdfour_result(&m, (uchar *)sum); + } +} + +void file_checksum(const char *fname, const STRUCT_STAT *st_p, char *sum) +{ + struct map_struct *buf; + OFF_T i, len = st_p->st_size; + md_context m; + int32 remainder; + int fd; + + memset(sum, 0, MAX_DIGEST_LEN); + + fd = do_open(fname, O_RDONLY, 0); + if (fd == -1) + return; + + buf = map_file(fd, len, MAX_MAP_SIZE, CSUM_CHUNK); + + if (protocol_version >= 30) { + md5_begin(&m); + + for (i = 0; i + CSUM_CHUNK <= len; i += CSUM_CHUNK) { + md5_update(&m, (uchar *)map_ptr(buf, i, CSUM_CHUNK), + CSUM_CHUNK); + } + + remainder = (int32)(len - i); + if (remainder > 0) + md5_update(&m, (uchar *)map_ptr(buf, i, remainder), remainder); + + md5_result(&m, (uchar *)sum); + } else { + mdfour_begin(&m); + + for (i = 0; i + CSUM_CHUNK <= len; i += CSUM_CHUNK) { + mdfour_update(&m, (uchar *)map_ptr(buf, i, CSUM_CHUNK), + CSUM_CHUNK); + } + + /* Prior to version 27 an incorrect MD4 checksum was computed + * by failing to call mdfour_tail() for block sizes that + * are multiples of 64. This is fixed by calling mdfour_update() + * even when there are no more bytes. */ + remainder = (int32)(len - i); + if (remainder > 0 || protocol_version >= 27) + mdfour_update(&m, (uchar *)map_ptr(buf, i, remainder), remainder); + + mdfour_result(&m, (uchar *)sum); + } + + close(fd); + unmap_file(buf); +} + +static int32 sumresidue; +static md_context md; + +void sum_init(int seed) +{ + char s[4]; + + if (protocol_version >= 30) + md5_begin(&md); + else { + mdfour_begin(&md); + sumresidue = 0; + SIVAL(s, 0, seed); + sum_update(s, 4); + } +} + +/** + * Feed data into an MD4 accumulator, md. The results may be + * retrieved using sum_end(). md is used for different purposes at + * different points during execution. + * + * @todo Perhaps get rid of md and just pass in the address each time. + * Very slightly clearer and slower. + **/ +void sum_update(const char *p, int32 len) +{ + if (protocol_version >= 30) { + md5_update(&md, (uchar *)p, len); + return; + } + + if (len + sumresidue < CSUM_CHUNK) { + memcpy(md.buffer + sumresidue, p, len); + sumresidue += len; + return; + } + + if (sumresidue) { + int32 i = CSUM_CHUNK - sumresidue; + memcpy(md.buffer + sumresidue, p, i); + mdfour_update(&md, (uchar *)md.buffer, CSUM_CHUNK); + len -= i; + p += i; + } + + while (len >= CSUM_CHUNK) { + mdfour_update(&md, (uchar *)p, CSUM_CHUNK); + len -= CSUM_CHUNK; + p += CSUM_CHUNK; + } + + sumresidue = len; + if (sumresidue) + memcpy(md.buffer, p, sumresidue); +} + +int sum_end(char *sum) +{ + if (protocol_version >= 30) { + md5_result(&md, (uchar *)sum); + return MD5_DIGEST_LEN; + } + + if (sumresidue || protocol_version >= 27) + mdfour_update(&md, (uchar *)md.buffer, sumresidue); + + mdfour_result(&md, (uchar *)sum); + + return MD4_DIGEST_LEN; +} diff --git a/rsync/chmod.c b/rsync/chmod.c new file mode 100644 index 0000000..1f73958 --- /dev/null +++ b/rsync/chmod.c @@ -0,0 +1,249 @@ +/* + * Implement the core of the --chmod option. + * + * Copyright (C) 2002 Scott Howard + * Copyright (C) 2005-2014 Wayne Davison + * + * 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, visit the http://fsf.org website. + */ + +#include "rsync.h" +#include "itypes.h" + +extern mode_t orig_umask; + +#define FLAG_X_KEEP (1<<0) +#define FLAG_DIRS_ONLY (1<<1) +#define FLAG_FILES_ONLY (1<<2) + +struct chmod_mode_struct { + struct chmod_mode_struct *next; + int ModeAND, ModeOR; + char flags; +}; + +#define CHMOD_ADD 1 +#define CHMOD_SUB 2 +#define CHMOD_EQ 3 +#define CHMOD_SET 4 + +#define STATE_ERROR 0 +#define STATE_1ST_HALF 1 +#define STATE_2ND_HALF 2 +#define STATE_OCTAL_NUM 3 + +/* Parse a chmod-style argument, and break it down into one or more AND/OR + * pairs in a linked list. We return a pointer to new items on succcess + * (appending the items to the specified list), or NULL on error. */ +struct chmod_mode_struct *parse_chmod(const char *modestr, + struct chmod_mode_struct **root_mode_ptr) +{ + int state = STATE_1ST_HALF; + int where = 0, what = 0, op = 0, topbits = 0, topoct = 0, flags = 0; + struct chmod_mode_struct *first_mode = NULL, *curr_mode = NULL, + *prev_mode = NULL; + + while (state != STATE_ERROR) { + if (!*modestr || *modestr == ',') { + int bits; + + if (!op) { + state = STATE_ERROR; + break; + } + prev_mode = curr_mode; + curr_mode = new_array(struct chmod_mode_struct, 1); + if (prev_mode) + prev_mode->next = curr_mode; + else + first_mode = curr_mode; + curr_mode->next = NULL; + + if (where) + bits = where * what; + else { + where = 0111; + bits = (where * what) & ~orig_umask; + } + + switch (op) { + case CHMOD_ADD: + curr_mode->ModeAND = CHMOD_BITS; + curr_mode->ModeOR = bits + topoct; + break; + case CHMOD_SUB: + curr_mode->ModeAND = CHMOD_BITS - bits - topoct; + curr_mode->ModeOR = 0; + break; + case CHMOD_EQ: + curr_mode->ModeAND = CHMOD_BITS - (where * 7) - (topoct ? topbits : 0); + curr_mode->ModeOR = bits + topoct; + break; + case CHMOD_SET: + curr_mode->ModeAND = 0; + curr_mode->ModeOR = bits; + break; + } + + curr_mode->flags = flags; + + if (!*modestr) + break; + modestr++; + + state = STATE_1ST_HALF; + where = what = op = topoct = topbits = flags = 0; + } + + switch (state) { + case STATE_1ST_HALF: + switch (*modestr) { + case 'D': + if (flags & FLAG_FILES_ONLY) + state = STATE_ERROR; + flags |= FLAG_DIRS_ONLY; + break; + case 'F': + if (flags & FLAG_DIRS_ONLY) + state = STATE_ERROR; + flags |= FLAG_FILES_ONLY; + break; + case 'u': + where |= 0100; + topbits |= 04000; + break; + case 'g': + where |= 0010; + topbits |= 02000; + break; + case 'o': + where |= 0001; + break; + case 'a': + where |= 0111; + break; + case '+': + op = CHMOD_ADD; + state = STATE_2ND_HALF; + break; + case '-': + op = CHMOD_SUB; + state = STATE_2ND_HALF; + break; + case '=': + op = CHMOD_EQ; + state = STATE_2ND_HALF; + break; + default: + if (isDigit(modestr) && *modestr < '8' && !where) { + op = CHMOD_SET; + state = STATE_OCTAL_NUM; + where = 1; + what = *modestr - '0'; + } else + state = STATE_ERROR; + break; + } + break; + case STATE_2ND_HALF: + switch (*modestr) { + case 'r': + what |= 4; + break; + case 'w': + what |= 2; + break; + case 'X': + flags |= FLAG_X_KEEP; + /* FALL THROUGH */ + case 'x': + what |= 1; + break; + case 's': + if (topbits) + topoct |= topbits; + else + topoct = 04000; + break; + case 't': + topoct |= 01000; + break; + default: + state = STATE_ERROR; + break; + } + break; + case STATE_OCTAL_NUM: + if (isDigit(modestr) && *modestr < '8') { + what = what*8 + *modestr - '0'; + if (what > CHMOD_BITS) + state = STATE_ERROR; + } else + state = STATE_ERROR; + break; + } + modestr++; + } + + if (state == STATE_ERROR) { + free_chmod_mode(first_mode); + return NULL; + } + + if (!(curr_mode = *root_mode_ptr)) + *root_mode_ptr = first_mode; + else { + while (curr_mode->next) + curr_mode = curr_mode->next; + curr_mode->next = first_mode; + } + + return first_mode; +} + + +/* Takes an existing file permission and a list of AND/OR changes, and + * create a new permissions. */ +int tweak_mode(int mode, struct chmod_mode_struct *chmod_modes) +{ + int IsX = mode & 0111; + int NonPerm = mode & ~CHMOD_BITS; + + for ( ; chmod_modes; chmod_modes = chmod_modes->next) { + if ((chmod_modes->flags & FLAG_DIRS_ONLY) && !S_ISDIR(NonPerm)) + continue; + if ((chmod_modes->flags & FLAG_FILES_ONLY) && S_ISDIR(NonPerm)) + continue; + mode &= chmod_modes->ModeAND; + if ((chmod_modes->flags & FLAG_X_KEEP) && !IsX && !S_ISDIR(NonPerm)) + mode |= chmod_modes->ModeOR & ~0111; + else + mode |= chmod_modes->ModeOR; + } + + return mode | NonPerm; +} + +/* Free the linked list created by parse_chmod. */ +int free_chmod_mode(struct chmod_mode_struct *chmod_modes) +{ + struct chmod_mode_struct *next; + + while (chmod_modes) { + next = chmod_modes->next; + free(chmod_modes); + chmod_modes = next; + } + return 0; +} diff --git a/rsync/cleanup.c b/rsync/cleanup.c new file mode 100644 index 0000000..cd023aa --- /dev/null +++ b/rsync/cleanup.c @@ -0,0 +1,292 @@ +/* + * End-of-run cleanup routines. + * + * Copyright (C) 1996-2000 Andrew Tridgell + * Copyright (C) 1996 Paul Mackerras + * Copyright (C) 2002 Martin Pool + * Copyright (C) 2003-2014 Wayne Davison + * + * 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, visit the http://fsf.org website. + */ + +#include "rsync.h" + +extern int am_server; +extern int am_daemon; +extern int am_receiver; +extern int io_error; +extern int keep_partial; +extern int got_xfer_error; +extern int protocol_version; +extern int output_needs_newline; +extern char *partial_dir; +extern char *logfile_name; + +BOOL shutting_down = False; +BOOL flush_ok_after_signal = False; + +#ifdef HAVE_SIGACTION +static struct sigaction sigact; +#endif + +/** + * Close all open sockets and files, allowing a (somewhat) graceful + * shutdown() of socket connections. This eliminates the abortive + * TCP RST sent by a Winsock-based system when the close() occurs. + **/ +void close_all(void) +{ +#ifdef SHUTDOWN_ALL_SOCKETS + int max_fd; + int fd; + int ret; + STRUCT_STAT st; + + max_fd = sysconf(_SC_OPEN_MAX) - 1; + for (fd = max_fd; fd >= 0; fd--) { + if ((ret = do_fstat(fd, &st)) == 0) { + if (is_a_socket(fd)) + ret = shutdown(fd, 2); + ret = close(fd); + } + } +#endif +} + +/** + * @file cleanup.c + * + * Code for handling interrupted transfers. Depending on the @c + * --partial option, we may either delete the temporary file, or go + * ahead and overwrite the destination. This second behaviour only + * occurs if we've sent literal data and therefore hopefully made + * progress on the transfer. + **/ + +/** + * Set to True once literal data has been sent across the link for the + * current file. (????) + * + * Handling the cleanup when a transfer is interrupted is tricky when + * --partial is selected. We need to ensure that the partial file is + * kept if any real data has been transferred. + **/ +int cleanup_got_literal = 0; + +static const char *cleanup_fname; +static const char *cleanup_new_fname; +static struct file_struct *cleanup_file; +static int cleanup_fd_r = -1, cleanup_fd_w = -1; +static pid_t cleanup_pid = 0; + +pid_t cleanup_child_pid = -1; + +/** + * Eventually calls exit(), passing @p code, therefore does not return. + * + * @param code one of the RERR_* codes from errcode.h. + **/ +NORETURN void _exit_cleanup(int code, const char *file, int line) +{ + static int switch_step = 0; + static int exit_code = 0, exit_line = 0; + static const char *exit_file = NULL; + static int first_code = 0; + + SIGACTION(SIGUSR1, SIG_IGN); + SIGACTION(SIGUSR2, SIG_IGN); + + if (!exit_code) { /* Preserve first error exit info when recursing. */ + exit_code = code; + exit_file = file; + exit_line = line < 0 ? -line : line; + } + + /* If this is the exit at the end of the run, the server side + * should not attempt to output a message (see log_exit()). */ + if (am_server && code == 0) + am_server = 2; + + /* Some of our actions might cause a recursive call back here, so we + * keep track of where we are in the cleanup and never repeat a step. */ + switch (switch_step) { +#include "case_N.h" /* case 0: */ + switch_step++; + + first_code = code; + + if (output_needs_newline) { + fputc('\n', stdout); + output_needs_newline = 0; + } + + if (DEBUG_GTE(EXIT, 2)) { + rprintf(FINFO, + "[%s] _exit_cleanup(code=%d, file=%s, line=%d): entered\n", + who_am_i(), code, file, line); + } + + /* FALLTHROUGH */ +#include "case_N.h" + switch_step++; + + if (cleanup_child_pid != -1) { + int status; + int pid = wait_process(cleanup_child_pid, &status, WNOHANG); + if (pid == cleanup_child_pid) { + status = WEXITSTATUS(status); + if (status > exit_code) + exit_code = status; + } + } + + /* FALLTHROUGH */ +#include "case_N.h" + switch_step++; + + if (cleanup_got_literal && (cleanup_fname || cleanup_fd_w != -1)) { + if (cleanup_fd_r != -1) { + close(cleanup_fd_r); + cleanup_fd_r = -1; + } + if (cleanup_fd_w != -1) { + flush_write_file(cleanup_fd_w); + close(cleanup_fd_w); + cleanup_fd_w = -1; + } + if (cleanup_fname && cleanup_new_fname && keep_partial + && handle_partial_dir(cleanup_new_fname, PDIR_CREATE)) { + int tweak_modtime = 0; + const char *fname = cleanup_fname; + cleanup_fname = NULL; + if (!partial_dir) { + /* We don't want to leave a partial file with a modern time or it + * could be skipped via --update. Setting the time to something + * really old also helps it to stand out as unfinished in an ls. */ + tweak_modtime = 1; + cleanup_file->modtime = 0; + } + finish_transfer(cleanup_new_fname, fname, NULL, NULL, + cleanup_file, tweak_modtime, !partial_dir); + } + } + + /* FALLTHROUGH */ +#include "case_N.h" + switch_step++; + + if (flush_ok_after_signal) { + flush_ok_after_signal = False; + if (code == RERR_SIGNAL) + io_flush(FULL_FLUSH); + } + if (!exit_code && !code) + io_flush(FULL_FLUSH); + + /* FALLTHROUGH */ +#include "case_N.h" + switch_step++; + + if (cleanup_fname) + do_unlink(cleanup_fname); + if (exit_code) + kill_all(SIGUSR1); + if (cleanup_pid && cleanup_pid == getpid()) { + char *pidf = lp_pid_file(); + if (pidf && *pidf) + unlink(lp_pid_file()); + } + + if (exit_code == 0) { + if (code) + exit_code = code; + if (io_error & IOERR_DEL_LIMIT) + exit_code = RERR_DEL_LIMIT; + if (io_error & IOERR_VANISHED) + exit_code = RERR_VANISHED; + if (io_error & IOERR_GENERAL || got_xfer_error) + exit_code = RERR_PARTIAL; + } + + /* If line < 0, this exit is after a MSG_ERROR_EXIT event, so + * we don't want to output a duplicate error. */ + if ((exit_code && line > 0) + || am_daemon || (logfile_name && (am_server || !INFO_GTE(STATS, 1)))) + log_exit(exit_code, exit_file, exit_line); + + /* FALLTHROUGH */ +#include "case_N.h" + switch_step++; + + if (DEBUG_GTE(EXIT, 1)) { + rprintf(FINFO, + "[%s] _exit_cleanup(code=%d, file=%s, line=%d): " + "about to call exit(%d)\n", + who_am_i(), first_code, exit_file, exit_line, exit_code); + } + + /* FALLTHROUGH */ +#include "case_N.h" + switch_step++; + + if (exit_code && exit_code != RERR_SOCKETIO && exit_code != RERR_STREAMIO && exit_code != RERR_SIGNAL1 + && exit_code != RERR_TIMEOUT && !shutting_down && (protocol_version >= 31 || am_receiver)) { + if (line > 0) { + if (DEBUG_GTE(EXIT, 3)) { + rprintf(FINFO, "[%s] sending MSG_ERROR_EXIT with exit_code %d\n", + who_am_i(), exit_code); + } + send_msg_int(MSG_ERROR_EXIT, exit_code); + } + noop_io_until_death(); + } + + /* FALLTHROUGH */ +#include "case_N.h" + switch_step++; + + if (am_server && exit_code) + msleep(100); + close_all(); + + /* FALLTHROUGH */ + default: + break; + } + + exit(exit_code); +} + +void cleanup_disable(void) +{ + cleanup_fname = cleanup_new_fname = NULL; + cleanup_fd_r = cleanup_fd_w = -1; + cleanup_got_literal = 0; +} + + +void cleanup_set(const char *fnametmp, const char *fname, struct file_struct *file, + int fd_r, int fd_w) +{ + cleanup_fname = fnametmp; + cleanup_new_fname = fname; /* can be NULL on a partial-dir failure */ + cleanup_file = file; + cleanup_fd_r = fd_r; + cleanup_fd_w = fd_w; +} + +void cleanup_set_pid(pid_t pid) +{ + cleanup_pid = pid; +} diff --git a/rsync/clientname.c b/rsync/clientname.c new file mode 100644 index 0000000..ccd43d7 --- /dev/null +++ b/rsync/clientname.c @@ -0,0 +1,348 @@ +/* + * Functions for looking up the remote name or addr of a socket. + * + * Copyright (C) 1992-2001 Andrew Tridgell + * Copyright (C) 2001, 2002 Martin Pool + * Copyright (C) 2002-2014 Wayne Davison + * + * 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, visit the http://fsf.org website. + */ + +/* + * This file is now converted to use the new-style getaddrinfo() + * interface, which supports IPv6 but is also supported on recent + * IPv4-only machines. On systems that don't have that interface, we + * emulate it using the KAME implementation. + */ + +#include "rsync.h" + +static const char default_name[] = "UNKNOWN"; +extern int am_server; + + +/** + * Return the IP addr of the client as a string + **/ +char *client_addr(int fd) +{ + static char addr_buf[100]; + static int initialised; + struct sockaddr_storage ss; + socklen_t length = sizeof ss; + + if (initialised) + return addr_buf; + + initialised = 1; + + if (am_server) { /* daemon over --rsh mode */ + char *env_str; + strlcpy(addr_buf, "0.0.0.0", sizeof addr_buf); + if ((env_str = getenv("REMOTE_HOST")) != NULL + || (env_str = getenv("SSH_CONNECTION")) != NULL + || (env_str = getenv("SSH_CLIENT")) != NULL + || (env_str = getenv("SSH2_CLIENT")) != NULL) { + char *p; + strlcpy(addr_buf, env_str, sizeof addr_buf); + /* Truncate the value to just the IP address. */ + if ((p = strchr(addr_buf, ' ')) != NULL) + *p = '\0'; + } + } else { + client_sockaddr(fd, &ss, &length); + getnameinfo((struct sockaddr *)&ss, length, + addr_buf, sizeof addr_buf, NULL, 0, NI_NUMERICHOST); + } + + return addr_buf; +} + + +static int get_sockaddr_family(const struct sockaddr_storage *ss) +{ + return ((struct sockaddr *) ss)->sa_family; +} + + +/** + * Return the DNS name of the client. + * + * The name is statically cached so that repeated lookups are quick, + * so there is a limit of one lookup per customer. + * + * If anything goes wrong, including the name->addr->name check, then + * we just use "UNKNOWN", so you can use that value in hosts allow + * lines. + * + * After translation from sockaddr to name we do a forward lookup to + * make sure nobody is spoofing PTR records. + **/ +char *client_name(int fd) +{ + static char name_buf[100]; + static char port_buf[100]; + static int initialised; + struct sockaddr_storage ss; + socklen_t ss_len; + + if (initialised) + return name_buf; + + strlcpy(name_buf, default_name, sizeof name_buf); + initialised = 1; + + memset(&ss, 0, sizeof ss); + + if (am_server) { /* daemon over --rsh mode */ + char *addr = client_addr(fd); + struct addrinfo hint, *answer; + int err; + + if (strcmp(addr, "0.0.0.0") == 0) + return name_buf; + + memset(&hint, 0, sizeof hint); + +#ifdef AI_NUMERICHOST + hint.ai_flags = AI_NUMERICHOST; +#endif + hint.ai_socktype = SOCK_STREAM; + + if ((err = getaddrinfo(addr, NULL, &hint, &answer)) != 0) { + rprintf(FLOG, "malformed address %s: %s\n", + addr, gai_strerror(err)); + return name_buf; + } + + switch (answer->ai_family) { + case AF_INET: + ss_len = sizeof (struct sockaddr_in); + memcpy(&ss, answer->ai_addr, ss_len); + break; +#ifdef INET6 + case AF_INET6: + ss_len = sizeof (struct sockaddr_in6); + memcpy(&ss, answer->ai_addr, ss_len); + break; +#endif + default: + exit_cleanup(RERR_SOCKETIO); + } + freeaddrinfo(answer); + } else { + ss_len = sizeof ss; + client_sockaddr(fd, &ss, &ss_len); + } + + if (lookup_name(fd, &ss, ss_len, name_buf, sizeof name_buf, + port_buf, sizeof port_buf) == 0) + check_name(fd, &ss, name_buf, sizeof name_buf); + + return name_buf; +} + + + +/** + * Get the sockaddr for the client. + * + * If it comes in as an ipv4 address mapped into IPv6 format then we + * convert it back to a regular IPv4. + **/ +void client_sockaddr(int fd, + struct sockaddr_storage *ss, + socklen_t *ss_len) +{ + memset(ss, 0, sizeof *ss); + + if (getpeername(fd, (struct sockaddr *) ss, ss_len)) { + /* FIXME: Can we really not continue? */ + rsyserr(FLOG, errno, "getpeername on fd%d failed", fd); + exit_cleanup(RERR_SOCKETIO); + } + +#ifdef INET6 + if (get_sockaddr_family(ss) == AF_INET6 && + IN6_IS_ADDR_V4MAPPED(&((struct sockaddr_in6 *)ss)->sin6_addr)) { + /* OK, so ss is in the IPv6 family, but it is really + * an IPv4 address: something like + * "::ffff:10.130.1.2". If we use it as-is, then the + * reverse lookup might fail or perhaps something else + * bad might happen. So instead we convert it to an + * equivalent address in the IPv4 address family. */ + struct sockaddr_in6 sin6; + struct sockaddr_in *sin; + + memcpy(&sin6, ss, sizeof sin6); + sin = (struct sockaddr_in *)ss; + memset(sin, 0, sizeof *sin); + sin->sin_family = AF_INET; + *ss_len = sizeof (struct sockaddr_in); +#ifdef HAVE_SOCKADDR_IN_LEN + sin->sin_len = *ss_len; +#endif + sin->sin_port = sin6.sin6_port; + + /* There is a macro to extract the mapped part + * (IN6_V4MAPPED_TO_SINADDR ?), but it does not seem + * to be present in the Linux headers. */ + memcpy(&sin->sin_addr, &sin6.sin6_addr.s6_addr[12], + sizeof sin->sin_addr); + } +#endif +} + + +/** + * Look up a name from @p ss into @p name_buf. + * + * @param fd file descriptor for client socket. + **/ +int lookup_name(int fd, const struct sockaddr_storage *ss, + socklen_t ss_len, + char *name_buf, size_t name_buf_size, + char *port_buf, size_t port_buf_size) +{ + int name_err; + + /* reverse lookup */ + name_err = getnameinfo((struct sockaddr *) ss, ss_len, + name_buf, name_buf_size, + port_buf, port_buf_size, + NI_NAMEREQD | NI_NUMERICSERV); + if (name_err != 0) { + strlcpy(name_buf, default_name, name_buf_size); + rprintf(FLOG, "name lookup failed for %s: %s\n", + client_addr(fd), gai_strerror(name_err)); + return name_err; + } + + return 0; +} + + + +/** + * Compare an addrinfo from the resolver to a sockinfo. + * + * Like strcmp, returns 0 for identical. + **/ +int compare_addrinfo_sockaddr(const struct addrinfo *ai, + const struct sockaddr_storage *ss) +{ + int ss_family = get_sockaddr_family(ss); + const char fn[] = "compare_addrinfo_sockaddr"; + + if (ai->ai_family != ss_family) { + rprintf(FLOG, "%s: response family %d != %d\n", + fn, ai->ai_family, ss_family); + return 1; + } + + /* The comparison method depends on the particular AF. */ + if (ss_family == AF_INET) { + const struct sockaddr_in *sin1, *sin2; + + sin1 = (const struct sockaddr_in *) ss; + sin2 = (const struct sockaddr_in *) ai->ai_addr; + + return memcmp(&sin1->sin_addr, &sin2->sin_addr, + sizeof sin1->sin_addr); + } + +#ifdef INET6 + if (ss_family == AF_INET6) { + const struct sockaddr_in6 *sin1, *sin2; + + sin1 = (const struct sockaddr_in6 *) ss; + sin2 = (const struct sockaddr_in6 *) ai->ai_addr; + + if (ai->ai_addrlen < sizeof (struct sockaddr_in6)) { + rprintf(FLOG, "%s: too short sockaddr_in6; length=%d\n", + fn, (int)ai->ai_addrlen); + return 1; + } + + if (memcmp(&sin1->sin6_addr, &sin2->sin6_addr, + sizeof sin1->sin6_addr)) + return 1; + +#ifdef HAVE_SOCKADDR_IN6_SCOPE_ID + if (sin1->sin6_scope_id != sin2->sin6_scope_id) + return 1; +#endif + return 0; + } +#endif /* INET6 */ + + /* don't know */ + return 1; +} + + +/** + * Do a forward lookup on @p name_buf and make sure it corresponds to + * @p ss -- otherwise we may be being spoofed. If we suspect we are, + * then we don't abort the connection but just emit a warning, and + * change @p name_buf to be "UNKNOWN". + * + * We don't do anything with the service when checking the name, + * because it doesn't seem that it could be spoofed in any way, and + * getaddrinfo on random service names seems to cause problems on AIX. + **/ +int check_name(int fd, + const struct sockaddr_storage *ss, + char *name_buf, size_t name_buf_size) +{ + struct addrinfo hints, *res, *res0; + int error; + int ss_family = get_sockaddr_family(ss); + + memset(&hints, 0, sizeof hints); + hints.ai_family = ss_family; + hints.ai_flags = AI_CANONNAME; + hints.ai_socktype = SOCK_STREAM; + error = getaddrinfo(name_buf, NULL, &hints, &res0); + if (error) { + rprintf(FLOG, "forward name lookup for %s failed: %s\n", + name_buf, gai_strerror(error)); + strlcpy(name_buf, default_name, name_buf_size); + return error; + } + + /* Given all these results, we expect that one of them will be + * the same as ss. The comparison is a bit complicated. */ + for (res = res0; res; res = res->ai_next) { + if (!compare_addrinfo_sockaddr(res, ss)) + break; /* OK, identical */ + } + + if (!res0) { + /* We hit the end of the list without finding an + * address that was the same as ss. */ + rprintf(FLOG, "no known address for \"%s\": " + "spoofed address?\n", name_buf); + strlcpy(name_buf, default_name, name_buf_size); + } else if (res == NULL) { + /* We hit the end of the list without finding an + * address that was the same as ss. */ + rprintf(FLOG, "%s is not a known address for \"%s\": " + "spoofed address?\n", client_addr(fd), name_buf); + strlcpy(name_buf, default_name, name_buf_size); + } + + freeaddrinfo(res0); + return 0; +} diff --git a/rsync/clientserver.c b/rsync/clientserver.c new file mode 100644 index 0000000..d9b5bb0 --- /dev/null +++ b/rsync/clientserver.c @@ -0,0 +1,1204 @@ +/* + * The socket based protocol for setting up a connection with rsyncd. + * + * Copyright (C) 1998-2001 Andrew Tridgell + * Copyright (C) 2001-2002 Martin Pool + * Copyright (C) 2002-2014 Wayne Davison + * + * 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, visit the http://fsf.org website. + */ + +#include "rsync.h" +#include "itypes.h" + +extern int quiet; +extern int dry_run; +extern int output_motd; +extern int list_only; +extern int am_sender; +extern int am_server; +extern int am_daemon; +extern int am_root; +extern int rsync_port; +extern int protect_args; +extern int ignore_errors; +extern int preserve_xattrs; +extern int kluge_around_eof; +extern int daemon_over_rsh; +extern int munge_symlinks; +extern int sanitize_paths; +extern int numeric_ids; +extern int filesfrom_fd; +extern int remote_protocol; +extern int protocol_version; +extern int io_timeout; +extern int no_detach; +extern int write_batch; +extern int default_af_hint; +extern int logfile_format_has_i; +extern int logfile_format_has_o_or_i; +extern char *bind_address; +extern char *config_file; +extern char *logfile_format; +extern char *files_from; +extern char *tmpdir; +extern struct chmod_mode_struct *chmod_modes; +extern filter_rule_list daemon_filter_list; +#ifdef ICONV_OPTION +extern char *iconv_opt; +extern iconv_t ic_send, ic_recv; +#endif + +#define MAX_GID_LIST 32 + +char *auth_user; +int read_only = 0; +int module_id = -1; +struct chmod_mode_struct *daemon_chmod_modes; + +/* module_dirlen is the length of the module_dir string when in daemon + * mode and module_dir is not "/"; otherwise 0. (Note that a chroot- + * enabled module can have a non-"/" module_dir these days.) */ +char *module_dir = NULL; +unsigned int module_dirlen = 0; + +char *full_module_path; + +static int rl_nulls = 0; + +#ifdef HAVE_SIGACTION +static struct sigaction sigact; +#endif + +static gid_t gid_list[MAX_GID_LIST]; +static int gid_count = 0; + +/* Used when "reverse lookup" is off. */ +const char undetermined_hostname[] = "UNDETERMINED"; + +/** + * Run a client connected to an rsyncd. The alternative to this + * function for remote-shell connections is do_cmd(). + * + * After negotiating which module to use and reading the server's + * motd, this hands over to client_run(). Telling the server the + * module will cause it to chroot/setuid/etc. + * + * Instead of doing a transfer, the client may at this stage instead + * get a listing of remote modules and exit. + * + * @return -1 for error in startup, or the result of client_run(). + * Either way, it eventually gets passed to exit_cleanup(). + **/ +int start_socket_client(char *host, int remote_argc, char *remote_argv[], + int argc, char *argv[]) +{ + int fd, ret; + char *p, *user = NULL; + + /* This is redundant with code in start_inband_exchange(), but this + * short-circuits a problem in the client before we open a socket, + * and the extra check won't hurt. */ + if (**remote_argv == '/') { + rprintf(FERROR, + "ERROR: The remote path must start with a module name not a /\n"); + return -1; + } + + if ((p = strrchr(host, '@')) != NULL) { + user = host; + host = p+1; + *p = '\0'; + } + + fd = open_socket_out_wrapped(host, rsync_port, bind_address, + default_af_hint); + if (fd == -1) + exit_cleanup(RERR_SOCKETIO); + +#ifdef ICONV_CONST + setup_iconv(); +#endif + + ret = start_inband_exchange(fd, fd, user, remote_argc, remote_argv); + + return ret ? ret : client_run(fd, fd, -1, argc, argv); +} + +static int exchange_protocols(int f_in, int f_out, char *buf, size_t bufsiz, int am_client) +{ + int remote_sub = -1; +#if SUBPROTOCOL_VERSION != 0 + int our_sub = protocol_version < PROTOCOL_VERSION ? 0 : SUBPROTOCOL_VERSION; +#else + int our_sub = 0; +#endif + char *motd; + + io_printf(f_out, "@RSYNCD: %d.%d\n", protocol_version, our_sub); + + if (!am_client) { + motd = lp_motd_file(); + if (motd && *motd) { + FILE *f = fopen(motd,"r"); + while (f && !feof(f)) { + int len = fread(buf, 1, bufsiz - 1, f); + if (len > 0) + write_buf(f_out, buf, len); + } + if (f) + fclose(f); + write_sbuf(f_out, "\n"); + } + } + + /* This strips the \n. */ + if (!read_line_old(f_in, buf, bufsiz, 0)) { + if (am_client) + rprintf(FERROR, "rsync: did not see server greeting\n"); + return -1; + } + + if (sscanf(buf, "@RSYNCD: %d.%d", &remote_protocol, &remote_sub) < 1) { + if (am_client) + rprintf(FERROR, "rsync: server sent \"%s\" rather than greeting\n", buf); + else + io_printf(f_out, "@ERROR: protocol startup error\n"); + return -1; + } + + if (remote_sub < 0) { + if (remote_protocol == 30) { + if (am_client) + rprintf(FERROR, "rsync: server is speaking an incompatible beta of protocol 30\n"); + else + io_printf(f_out, "@ERROR: your client is speaking an incompatible beta of protocol 30\n"); + return -1; + } + remote_sub = 0; + } + + if (protocol_version > remote_protocol) { + protocol_version = remote_protocol; + if (remote_sub) + protocol_version--; + } else if (protocol_version == remote_protocol) { + if (remote_sub != our_sub) + protocol_version--; + } +#if SUBPROTOCOL_VERSION != 0 + else if (protocol_version < remote_protocol) { + if (our_sub) + protocol_version--; + } +#endif + + if (protocol_version >= 30) + rl_nulls = 1; + + return 0; +} + +int start_inband_exchange(int f_in, int f_out, const char *user, int argc, char *argv[]) +{ + int i, modlen; + char line[BIGPATHBUFLEN]; + char *sargs[MAX_ARGS]; + int sargc = 0; + char *p, *modname; + + assert(argc > 0 && *argv != NULL); + + if (**argv == '/') { + rprintf(FERROR, + "ERROR: The remote path must start with a module name\n"); + return -1; + } + + if (!(p = strchr(*argv, '/'))) + modlen = strlen(*argv); + else + modlen = p - *argv; + + if (!(modname = new_array(char, modlen+1+1))) /* room for '/' & '\0' */ + out_of_memory("start_inband_exchange"); + strlcpy(modname, *argv, modlen + 1); + modname[modlen] = '/'; + modname[modlen+1] = '\0'; + + if (!user) + user = getenv("USER"); + if (!user) + user = getenv("LOGNAME"); + + if (exchange_protocols(f_in, f_out, line, sizeof line, 1) < 0) + return -1; + + /* set daemon_over_rsh to false since we need to build the + * true set of args passed through the rsh/ssh connection; + * this is a no-op for direct-socket-connection mode */ + daemon_over_rsh = 0; + server_options(sargs, &sargc); + + if (sargc >= MAX_ARGS - 2) + goto arg_overflow; + + sargs[sargc++] = "."; + + while (argc > 0) { + if (sargc >= MAX_ARGS - 1) { + arg_overflow: + rprintf(FERROR, "internal: args[] overflowed in do_cmd()\n"); + exit_cleanup(RERR_SYNTAX); + } + if (strncmp(*argv, modname, modlen) == 0 + && argv[0][modlen] == '\0') + sargs[sargc++] = modname; /* we send "modname/" */ + else if (**argv == '-') { + if (asprintf(sargs + sargc++, "./%s", *argv) < 0) + out_of_memory("start_inband_exchange"); + } else + sargs[sargc++] = *argv; + argv++; + argc--; + } + + sargs[sargc] = NULL; + + if (DEBUG_GTE(CMD, 1)) + print_child_argv("sending daemon args:", sargs); + + io_printf(f_out, "%.*s\n", modlen, modname); + + /* Old servers may just drop the connection here, + rather than sending a proper EXIT command. Yuck. */ + kluge_around_eof = list_only && protocol_version < 25 ? 1 : 0; + + while (1) { + if (!read_line_old(f_in, line, sizeof line, 0)) { + rprintf(FERROR, "rsync: didn't get server startup line\n"); + return -1; + } + + if (strncmp(line,"@RSYNCD: AUTHREQD ",18) == 0) { + auth_client(f_out, user, line+18); + continue; + } + + if (strcmp(line,"@RSYNCD: OK") == 0) + break; + + if (strcmp(line,"@RSYNCD: EXIT") == 0) { + /* This is sent by recent versions of the + * server to terminate the listing of modules. + * We don't want to go on and transfer + * anything; just exit. */ + exit(0); + } + + if (strncmp(line, "@ERROR", 6) == 0) { + rprintf(FERROR, "%s\n", line); + /* This is always fatal; the server will now + * close the socket. */ + return -1; + } + + /* This might be a MOTD line or a module listing, but there is + * no way to differentiate it. The manpage mentions this. */ + if (output_motd) + rprintf(FINFO, "%s\n", line); + } + kluge_around_eof = 0; + + if (rl_nulls) { + for (i = 0; i < sargc; i++) { + if (!sargs[i]) /* stop at --protect-args NULL */ + break; + write_sbuf(f_out, sargs[i]); + write_byte(f_out, 0); + } + write_byte(f_out, 0); + } else { + for (i = 0; i < sargc; i++) + io_printf(f_out, "%s\n", sargs[i]); + write_sbuf(f_out, "\n"); + } + + if (protect_args) + send_protected_args(f_out, sargs); + + if (protocol_version < 23) { + if (protocol_version == 22 || !am_sender) + io_start_multiplex_in(f_in); + } + + free(modname); + + return 0; +} + +static char *finish_pre_exec(pid_t pid, int write_fd, int read_fd, char *request, + char **early_argv, char **argv) +{ + char buf[BIGPATHBUFLEN], *bp; + int j = 0, status = -1, msglen = sizeof buf - 1; + + if (!request) + request = "(NONE)"; + + write_buf(write_fd, request, strlen(request)+1); + if (early_argv) { + for ( ; *early_argv; early_argv++) + write_buf(write_fd, *early_argv, strlen(*early_argv)+1); + j = 1; /* Skip arg0 name in argv. */ + } + for ( ; argv[j]; j++) + write_buf(write_fd, argv[j], strlen(argv[j])+1); + write_byte(write_fd, 0); + + close(write_fd); + + /* Read the stdout from the pre-xfer exec program. This it is only + * displayed to the user if the script also returns an error status. */ + for (bp = buf; msglen > 0; msglen -= j) { + if ((j = read(read_fd, bp, msglen)) <= 0) { + if (j == 0) + break; + if (errno == EINTR) + continue; + break; /* Just ignore the read error for now... */ + } + bp += j; + if (j > 1 && bp[-1] == '\n' && bp[-2] == '\r') { + bp--; + j--; + bp[-1] = '\n'; + } + } + *bp = '\0'; + + close(read_fd); + + if (wait_process(pid, &status, 0) < 0 + || !WIFEXITED(status) || WEXITSTATUS(status) != 0) { + char *e; + if (asprintf(&e, "pre-xfer exec returned failure (%d)%s%s%s\n%s", + status, status < 0 ? ": " : "", + status < 0 ? strerror(errno) : "", + *buf ? ":" : "", buf) < 0) + return "out_of_memory in finish_pre_exec\n"; + return e; + } + return NULL; +} + +#ifdef HAVE_PUTENV +static int read_arg_from_pipe(int fd, char *buf, int limit) +{ + char *bp = buf, *eob = buf + limit - 1; + + while (1) { + int got = read(fd, bp, 1); + if (got != 1) { + if (got < 0 && errno == EINTR) + continue; + return -1; + } + if (*bp == '\0') + break; + if (bp < eob) + bp++; + } + *bp = '\0'; + + return bp - buf; +} +#endif + +static int path_failure(int f_out, const char *dir, BOOL was_chdir) +{ + if (was_chdir) + rsyserr(FLOG, errno, "chdir %s failed\n", dir); + else + rprintf(FLOG, "normalize_path(%s) failed\n", dir); + io_printf(f_out, "@ERROR: chdir failed\n"); + return -1; +} + +static int add_a_group(int f_out, const char *gname) +{ + gid_t gid; + if (!group_to_gid(gname, &gid, True)) { + rprintf(FLOG, "Invalid gid %s\n", gname); + io_printf(f_out, "@ERROR: invalid gid %s\n", gname); + return -1; + } + if (gid_count == MAX_GID_LIST) { + rprintf(FLOG, "Too many groups specified via gid parameter.\n"); + io_printf(f_out, "@ERROR: too many groups\n"); + return -1; + } + gid_list[gid_count++] = gid; + return 0; +} + +#ifdef HAVE_GETGROUPLIST +static int want_all_groups(int f_out, uid_t uid) +{ + const char *err; + gid_count = MAX_GID_LIST; + if ((err = getallgroups(uid, gid_list, &gid_count)) != NULL) { + rsyserr(FLOG, errno, "%s", err); + io_printf(f_out, "@ERROR: %s\n", err); + return -1; + } + return 0; +} +#elif defined HAVE_INITGROUPS +static struct passwd *want_all_groups(int f_out, uid_t uid) +{ + struct passwd *pw; + if ((pw = getpwuid(uid)) == NULL) { + rsyserr(FLOG, errno, "getpwuid failed"); + io_printf(f_out, "@ERROR: getpwuid failed\n"); + return NULL; + } + /* Start with the default group and initgroups() will add the reset. */ + gid_count = 1; + gid_list[0] = pw->pw_gid; + return pw; +} +#endif + +static void set_env_str(const char *var, const char *str) +{ +#ifdef HAVE_PUTENV + char *mem; + if (asprintf(&mem, "%s=%s", var, str) < 0) + out_of_memory("set_env_str"); + putenv(mem); +#endif +} + +#ifdef HAVE_PUTENV +static void set_env_num(const char *var, long num) +{ + char *mem; + if (asprintf(&mem, "%s=%ld", var, num) < 0) + out_of_memory("set_env_num"); + putenv(mem); +} +#endif + +static int rsync_module(int f_in, int f_out, int i, const char *addr, const char *host) +{ + int argc; + char **argv, **orig_argv, **orig_early_argv, *module_chdir; + char line[BIGPATHBUFLEN]; +#if defined HAVE_INITGROUPS && !defined HAVE_GETGROUPLIST + struct passwd *pw = NULL; +#endif + uid_t uid; + int set_uid; + char *p, *err_msg = NULL; + char *name = lp_name(i); + int use_chroot = lp_use_chroot(i); + int ret, pre_exec_arg_fd = -1, pre_exec_error_fd = -1; + int save_munge_symlinks; + pid_t pre_exec_pid = 0; + char *request = NULL; + + set_env_str("RSYNC_MODULE_NAME", name); + +#ifdef ICONV_OPTION + iconv_opt = lp_charset(i); + if (*iconv_opt) + setup_iconv(); + iconv_opt = NULL; +#endif + + /* If reverse lookup is disabled globally but enabled for this module, + * we need to do it now before the access check. */ + if (host == undetermined_hostname && lp_reverse_lookup(i)) + host = client_name(f_in); + set_env_str("RSYNC_HOST_NAME", host); + set_env_str("RSYNC_HOST_ADDR", addr); + + if (!allow_access(addr, &host, i)) { + rprintf(FLOG, "rsync denied on module %s from %s (%s)\n", + name, host, addr); + if (!lp_list(i)) + io_printf(f_out, "@ERROR: Unknown module '%s'\n", name); + else { + io_printf(f_out, + "@ERROR: access denied to %s from %s (%s)\n", + name, host, addr); + } + return -1; + } + + if (am_daemon && am_server) { + rprintf(FLOG, "rsync allowed access on module %s from %s (%s)\n", + name, host, addr); + } + + if (!claim_connection(lp_lock_file(i), lp_max_connections(i))) { + if (errno) { + rsyserr(FLOG, errno, "failed to open lock file %s", + lp_lock_file(i)); + io_printf(f_out, "@ERROR: failed to open lock file\n"); + } else { + rprintf(FLOG, "max connections (%d) reached\n", + lp_max_connections(i)); + io_printf(f_out, "@ERROR: max connections (%d) reached -- try again later\n", + lp_max_connections(i)); + } + return -1; + } + + read_only = lp_read_only(i); /* may also be overridden by auth_server() */ + auth_user = auth_server(f_in, f_out, i, host, addr, "@RSYNCD: AUTHREQD "); + + if (!auth_user) { + io_printf(f_out, "@ERROR: auth failed on module %s\n", name); + return -1; + } + set_env_str("RSYNC_USER_NAME", auth_user); + + module_id = i; + + if (lp_transfer_logging(i) && !logfile_format) + logfile_format = lp_log_format(i); + if (log_format_has(logfile_format, 'i')) + logfile_format_has_i = 1; + if (logfile_format_has_i || log_format_has(logfile_format, 'o')) + logfile_format_has_o_or_i = 1; + + uid = MY_UID(); + am_root = (uid == 0); + + p = *lp_uid(i) ? lp_uid(i) : am_root ? NOBODY_USER : NULL; + if (p) { + if (!user_to_uid(p, &uid, True)) { + rprintf(FLOG, "Invalid uid %s\n", p); + io_printf(f_out, "@ERROR: invalid uid %s\n", p); + return -1; + } + set_uid = 1; + } else + set_uid = 0; + + p = *lp_gid(i) ? strtok(lp_gid(i), ", ") : NULL; + if (p) { + /* The "*" gid must be the first item in the list. */ + if (strcmp(p, "*") == 0) { +#ifdef HAVE_GETGROUPLIST + if (want_all_groups(f_out, uid) < 0) + return -1; +#elif defined HAVE_INITGROUPS + if ((pw = want_all_groups(f_out, uid)) == NULL) + return -1; +#else + rprintf(FLOG, "This rsync does not support a gid of \"*\"\n"); + io_printf(f_out, "@ERROR: invalid gid setting.\n"); + return -1; +#endif + } else if (add_a_group(f_out, p) < 0) + return -1; + while ((p = strtok(NULL, ", ")) != NULL) { +#if defined HAVE_INITGROUPS && !defined HAVE_GETGROUPLIST + if (pw) { + rprintf(FLOG, "This rsync cannot add groups after \"*\".\n"); + io_printf(f_out, "@ERROR: invalid gid setting.\n"); + return -1; + } +#endif + if (add_a_group(f_out, p) < 0) + return -1; + } + } else if (am_root) { + if (add_a_group(f_out, NOBODY_GROUP) < 0) + return -1; + } + + module_dir = lp_path(i); + if (*module_dir == '\0') { + rprintf(FLOG, "No path specified for module %s\n", name); + io_printf(f_out, "@ERROR: no path setting.\n"); + return -1; + } + if (use_chroot) { + if ((p = strstr(module_dir, "/./")) != NULL) { + *p = '\0'; /* Temporary... */ + if (!(module_chdir = normalize_path(module_dir, True, NULL))) + return path_failure(f_out, module_dir, False); + *p = '/'; + if (!(p = normalize_path(p + 2, True, &module_dirlen))) + return path_failure(f_out, strstr(module_dir, "/./"), False); + if (!(full_module_path = normalize_path(module_dir, False, NULL))) + full_module_path = module_dir; + module_dir = p; + } else { + if (!(module_chdir = normalize_path(module_dir, False, NULL))) + return path_failure(f_out, module_dir, False); + full_module_path = module_chdir; + module_dir = "/"; + module_dirlen = 1; + } + } else { + if (!(module_chdir = normalize_path(module_dir, False, &module_dirlen))) + return path_failure(f_out, module_dir, False); + full_module_path = module_dir = module_chdir; + } + set_env_str("RSYNC_MODULE_PATH", full_module_path); + + if (module_dirlen == 1) { + module_dirlen = 0; + set_filter_dir("/", 1); + } else + set_filter_dir(module_dir, module_dirlen); + + p = lp_filter(i); + parse_filter_str(&daemon_filter_list, p, rule_template(FILTRULE_WORD_SPLIT), + XFLG_ABS_IF_SLASH | XFLG_DIR2WILD3); + + p = lp_include_from(i); + parse_filter_file(&daemon_filter_list, p, rule_template(FILTRULE_INCLUDE), + XFLG_ABS_IF_SLASH | XFLG_DIR2WILD3 | XFLG_OLD_PREFIXES | XFLG_FATAL_ERRORS); + + p = lp_include(i); + parse_filter_str(&daemon_filter_list, p, + rule_template(FILTRULE_INCLUDE | FILTRULE_WORD_SPLIT), + XFLG_ABS_IF_SLASH | XFLG_DIR2WILD3 | XFLG_OLD_PREFIXES); + + p = lp_exclude_from(i); + parse_filter_file(&daemon_filter_list, p, rule_template(0), + XFLG_ABS_IF_SLASH | XFLG_DIR2WILD3 | XFLG_OLD_PREFIXES | XFLG_FATAL_ERRORS); + + p = lp_exclude(i); + parse_filter_str(&daemon_filter_list, p, rule_template(FILTRULE_WORD_SPLIT), + XFLG_ABS_IF_SLASH | XFLG_DIR2WILD3 | XFLG_OLD_PREFIXES); + + log_init(1); + +#ifdef HAVE_PUTENV + if (*lp_prexfer_exec(i) || *lp_postxfer_exec(i)) { + int status; + + /* For post-xfer exec, fork a new process to run the rsync + * daemon while this process waits for the exit status and + * runs the indicated command at that point. */ + if (*lp_postxfer_exec(i)) { + pid_t pid = fork(); + if (pid < 0) { + rsyserr(FLOG, errno, "fork failed"); + io_printf(f_out, "@ERROR: fork failed\n"); + return -1; + } + if (pid) { + close(f_in); + if (f_out != f_in) + close(f_out); + set_env_num("RSYNC_PID", (long)pid); + if (wait_process(pid, &status, 0) < 0) + status = -1; + set_env_num("RSYNC_RAW_STATUS", status); + if (WIFEXITED(status)) + status = WEXITSTATUS(status); + else + status = -1; + set_env_num("RSYNC_EXIT_STATUS", status); + if (system(lp_postxfer_exec(i)) < 0) + status = -1; + _exit(status); + } + } + /* For pre-xfer exec, fork a child process to run the indicated + * command, though it first waits for the parent process to + * send us the user's request via a pipe. */ + if (*lp_prexfer_exec(i)) { + int arg_fds[2], error_fds[2]; + set_env_num("RSYNC_PID", (long)getpid()); + if (pipe(arg_fds) < 0 || pipe(error_fds) < 0 || (pre_exec_pid = fork()) < 0) { + rsyserr(FLOG, errno, "pre-xfer exec preparation failed"); + io_printf(f_out, "@ERROR: pre-xfer exec preparation failed\n"); + return -1; + } + if (pre_exec_pid == 0) { + char buf[BIGPATHBUFLEN]; + int j, len; + close(arg_fds[1]); + close(error_fds[0]); + pre_exec_arg_fd = arg_fds[0]; + pre_exec_error_fd = error_fds[1]; + set_blocking(pre_exec_arg_fd); + set_blocking(pre_exec_error_fd); + len = read_arg_from_pipe(pre_exec_arg_fd, buf, BIGPATHBUFLEN); + if (len <= 0) + _exit(1); + set_env_str("RSYNC_REQUEST", buf); + for (j = 0; ; j++) { + len = read_arg_from_pipe(pre_exec_arg_fd, buf, + BIGPATHBUFLEN); + if (len <= 0) { + if (!len) + break; + _exit(1); + } + if (asprintf(&p, "RSYNC_ARG%d=%s", j, buf) >= 0) + putenv(p); + } + close(pre_exec_arg_fd); + close(STDIN_FILENO); + dup2(pre_exec_error_fd, STDOUT_FILENO); + close(pre_exec_error_fd); + status = system(lp_prexfer_exec(i)); + if (!WIFEXITED(status)) + _exit(1); + _exit(WEXITSTATUS(status)); + } + close(arg_fds[0]); + close(error_fds[1]); + pre_exec_arg_fd = arg_fds[1]; + pre_exec_error_fd = error_fds[0]; + set_blocking(pre_exec_arg_fd); + set_blocking(pre_exec_error_fd); + } + } +#endif + + if (use_chroot) { + /* + * XXX: The 'use chroot' flag is a fairly reliable + * source of confusion, because it fails under two + * important circumstances: running as non-root, + * running on Win32 (or possibly others). On the + * other hand, if you are running as root, then it + * might be better to always use chroot. + * + * So, perhaps if we can't chroot we should just issue + * a warning, unless a "require chroot" flag is set, + * in which case we fail. + */ + if (chroot(module_chdir)) { + rsyserr(FLOG, errno, "chroot %s failed", module_chdir); + io_printf(f_out, "@ERROR: chroot failed\n"); + return -1; + } + module_chdir = module_dir; + } + + if (!change_dir(module_chdir, CD_NORMAL)) + return path_failure(f_out, module_chdir, True); + if (module_dirlen || !use_chroot) + sanitize_paths = 1; + + if ((munge_symlinks = lp_munge_symlinks(i)) < 0) + munge_symlinks = !use_chroot || module_dirlen; + if (munge_symlinks) { + STRUCT_STAT st; + char prefix[SYMLINK_PREFIX_LEN]; /* NOT +1 ! */ + strlcpy(prefix, SYMLINK_PREFIX, sizeof prefix); /* trim the trailing slash */ + if (do_stat(prefix, &st) == 0 && S_ISDIR(st.st_mode)) { + rprintf(FLOG, "Symlink munging is unsafe when a %s directory exists.\n", + prefix); + io_printf(f_out, "@ERROR: daemon security issue -- contact admin\n", name); + exit_cleanup(RERR_UNSUPPORTED); + } + } + + if (gid_count) { + if (setgid(gid_list[0])) { + rsyserr(FLOG, errno, "setgid %ld failed", (long)gid_list[0]); + io_printf(f_out, "@ERROR: setgid failed\n"); + return -1; + } +#ifdef HAVE_SETGROUPS + /* Set the group(s) we want to be active. */ + if (setgroups(gid_count, gid_list)) { + rsyserr(FLOG, errno, "setgroups failed"); + io_printf(f_out, "@ERROR: setgroups failed\n"); + return -1; + } +#endif +#if defined HAVE_INITGROUPS && !defined HAVE_GETGROUPLIST + /* pw is set if the user wants all the user's groups. */ + if (pw && initgroups(pw->pw_name, pw->pw_gid) < 0) { + rsyserr(FLOG, errno, "initgroups failed"); + io_printf(f_out, "@ERROR: initgroups failed\n"); + return -1; + } +#endif + } + + if (set_uid) { + if (setuid(uid) < 0 +#ifdef HAVE_SETEUID + || seteuid(uid) < 0 +#endif + ) { + rsyserr(FLOG, errno, "setuid %ld failed", (long)uid); + io_printf(f_out, "@ERROR: setuid failed\n"); + return -1; + } + + am_root = (MY_UID() == 0); + } + + if (lp_temp_dir(i) && *lp_temp_dir(i)) { + tmpdir = lp_temp_dir(i); + if (strlen(tmpdir) >= MAXPATHLEN - 10) { + rprintf(FLOG, + "the 'temp dir' value for %s is WAY too long -- ignoring.\n", + name); + tmpdir = NULL; + } + } + + io_printf(f_out, "@RSYNCD: OK\n"); + + read_args(f_in, name, line, sizeof line, rl_nulls, &argv, &argc, &request); + orig_argv = argv; + + save_munge_symlinks = munge_symlinks; + + reset_output_levels(); /* future verbosity is controlled by client options */ + ret = parse_arguments(&argc, (const char ***) &argv); + if (protect_args && ret) { + orig_early_argv = orig_argv; + protect_args = 2; + read_args(f_in, name, line, sizeof line, 1, &argv, &argc, &request); + orig_argv = argv; + ret = parse_arguments(&argc, (const char ***) &argv); + } else + orig_early_argv = NULL; + + munge_symlinks = save_munge_symlinks; /* The client mustn't control this. */ + + if (pre_exec_pid) { + err_msg = finish_pre_exec(pre_exec_pid, pre_exec_arg_fd, pre_exec_error_fd, + request, orig_early_argv, orig_argv); + } + + if (orig_early_argv) + free(orig_early_argv); + + am_server = 1; /* Don't let someone try to be tricky. */ + quiet = 0; + if (lp_ignore_errors(module_id)) + ignore_errors = 1; + if (write_batch < 0) + dry_run = 1; + + if (lp_fake_super(i)) { + if (preserve_xattrs > 1) + preserve_xattrs = 1; + am_root = -1; + } else if (am_root < 0) /* Treat --fake-super from client as --super. */ + am_root = 2; + + if (filesfrom_fd == 0) + filesfrom_fd = f_in; + + if (request) { + if (*auth_user) { + rprintf(FLOG, "rsync %s %s from %s@%s (%s)\n", + am_sender ? "on" : "to", + request, auth_user, host, addr); + } else { + rprintf(FLOG, "rsync %s %s from %s (%s)\n", + am_sender ? "on" : "to", + request, host, addr); + } + free(request); + } + +#ifndef DEBUG + /* don't allow the logs to be flooded too fast */ + limit_output_verbosity(lp_max_verbosity(i)); +#endif + + if (protocol_version < 23 + && (protocol_version == 22 || am_sender)) + io_start_multiplex_out(f_out); + else if (!ret || err_msg) { + /* We have to get I/O multiplexing started so that we can + * get the error back to the client. This means getting + * the protocol setup finished first in later versions. */ + setup_protocol(f_out, f_in); + if (!am_sender) { + /* Since we failed in our option parsing, we may not + * have finished parsing that the client sent us a + * --files-from option, so look for it manually. + * Without this, the socket would be in the wrong + * state for the upcoming error message. */ + if (!files_from) { + int i; + for (i = 0; i < argc; i++) { + if (strncmp(argv[i], "--files-from", 12) == 0) { + files_from = ""; + break; + } + } + } + if (files_from) + write_byte(f_out, 0); + } + io_start_multiplex_out(f_out); + } + + if (!ret || err_msg) { + if (err_msg) { + while ((p = strchr(err_msg, '\n')) != NULL) { + int len = p - err_msg + 1; + rwrite(FERROR, err_msg, len, 0); + err_msg += len; + } + if (*err_msg) + rprintf(FERROR, "%s\n", err_msg); + } else + option_error(); + msleep(400); + exit_cleanup(RERR_UNSUPPORTED); + } + +#ifdef ICONV_OPTION + if (!iconv_opt) { + if (ic_send != (iconv_t)-1) { + iconv_close(ic_send); + ic_send = (iconv_t)-1; + } + if (ic_recv != (iconv_t)-1) { + iconv_close(ic_recv); + ic_recv = (iconv_t)-1; + } + } +#endif + + if (!numeric_ids + && (use_chroot ? lp_numeric_ids(i) != False : lp_numeric_ids(i) == True)) + numeric_ids = -1; /* Set --numeric-ids w/o breaking protocol. */ + + if (lp_timeout(i) && (!io_timeout || lp_timeout(i) < io_timeout)) + set_io_timeout(lp_timeout(i)); + + /* If we have some incoming/outgoing chmod changes, append them to + * any user-specified changes (making our changes have priority). + * We also get a pointer to just our changes so that a receiver + * process can use them separately if --perms wasn't specified. */ + if (am_sender) + p = lp_outgoing_chmod(i); + else + p = lp_incoming_chmod(i); + if (*p && !(daemon_chmod_modes = parse_chmod(p, &chmod_modes))) { + rprintf(FLOG, "Invalid \"%sing chmod\" directive: %s\n", + am_sender ? "outgo" : "incom", p); + } + + start_server(f_in, f_out, argc, argv); + + return 0; +} + +/* send a list of available modules to the client. Don't list those + with "list = False". */ +static void send_listing(int fd) +{ + int n = lp_num_modules(); + int i; + + for (i = 0; i < n; i++) { + if (lp_list(i)) + io_printf(fd, "%-15s\t%s\n", lp_name(i), lp_comment(i)); + } + + if (protocol_version >= 25) + io_printf(fd,"@RSYNCD: EXIT\n"); +} + +static int load_config(int globals_only) +{ + if (!config_file) { + if (am_server && am_root <= 0) + config_file = RSYNCD_USERCONF; + else + config_file = RSYNCD_SYSCONF; + } + return lp_load(config_file, globals_only); +} + +/* this is called when a connection is established to a client + and we want to start talking. The setup of the system is done from + here */ +int start_daemon(int f_in, int f_out) +{ + char line[1024]; + const char *addr, *host; + int i; + + io_set_sock_fds(f_in, f_out); + + /* We must load the config file before calling any function that + * might cause log-file output to occur. This ensures that the + * "log file" param gets honored for the 2 non-forked use-cases + * (when rsync is run by init and run by a remote shell). */ + if (!load_config(0)) + exit_cleanup(RERR_SYNTAX); + + addr = client_addr(f_in); + host = lp_reverse_lookup(-1) ? client_name(f_in) : undetermined_hostname; + rprintf(FLOG, "connect from %s (%s)\n", host, addr); + + if (!am_server) { + set_socket_options(f_in, "SO_KEEPALIVE"); + set_nonblocking(f_in); + } + + if (exchange_protocols(f_in, f_out, line, sizeof line, 0) < 0) + return -1; + + line[0] = 0; + if (!read_line_old(f_in, line, sizeof line, 0)) + return -1; + + if (!*line || strcmp(line, "#list") == 0) { + rprintf(FLOG, "module-list request from %s (%s)\n", + host, addr); + send_listing(f_out); + return -1; + } + + if (*line == '#') { + /* it's some sort of command that I don't understand */ + io_printf(f_out, "@ERROR: Unknown command '%s'\n", line); + return -1; + } + + if ((i = lp_number(line)) < 0) { + rprintf(FLOG, "unknown module '%s' tried from %s (%s)\n", + line, host, addr); + io_printf(f_out, "@ERROR: Unknown module '%s'\n", line); + return -1; + } + +#ifdef HAVE_SIGACTION + sigact.sa_flags = SA_NOCLDSTOP; +#endif + SIGACTION(SIGCHLD, remember_children); + + return rsync_module(f_in, f_out, i, addr, host); +} + +static void create_pid_file(void) +{ + char *pid_file = lp_pid_file(); + char pidbuf[16]; + pid_t pid = getpid(); + int fd, len; + + if (!pid_file || !*pid_file) + return; + + cleanup_set_pid(pid); + if ((fd = do_open(pid_file, O_WRONLY|O_CREAT|O_EXCL, 0666)) == -1) { + failure: + cleanup_set_pid(0); + fprintf(stderr, "failed to create pid file %s: %s\n", pid_file, strerror(errno)); + rsyserr(FLOG, errno, "failed to create pid file %s", pid_file); + exit_cleanup(RERR_FILEIO); + } + snprintf(pidbuf, sizeof pidbuf, "%d\n", (int)pid); + len = strlen(pidbuf); + if (write(fd, pidbuf, len) != len) + goto failure; + close(fd); +} + +/* Become a daemon, discarding the controlling terminal. */ +static void become_daemon(void) +{ + int i; + pid_t pid = fork(); + + if (pid) { + if (pid < 0) { + fprintf(stderr, "failed to fork: %s\n", strerror(errno)); + exit_cleanup(RERR_FILEIO); + } + _exit(0); + } + + create_pid_file(); + + /* detach from the terminal */ +#ifdef HAVE_SETSID + setsid(); +#elif defined TIOCNOTTY + i = open("/dev/tty", O_RDWR); + if (i >= 0) { + ioctl(i, (int)TIOCNOTTY, (char *)0); + close(i); + } +#endif + /* make sure that stdin, stdout an stderr don't stuff things + * up (library functions, for example) */ + for (i = 0; i < 3; i++) { + close(i); + open("/dev/null", O_RDWR); + } +} + +int daemon_main(void) +{ + if (is_a_socket(STDIN_FILENO)) { + int i; + + /* we are running via inetd - close off stdout and + * stderr so that library functions (and getopt) don't + * try to use them. Redirect them to /dev/null */ + for (i = 1; i < 3; i++) { + close(i); + open("/dev/null", O_RDWR); + } + + return start_daemon(STDIN_FILENO, STDIN_FILENO); + } + + if (!load_config(1)) { + fprintf(stderr, "Failed to parse config file: %s\n", config_file); + exit_cleanup(RERR_SYNTAX); + } + set_dparams(0); + + if (no_detach) + create_pid_file(); + else + become_daemon(); + + if (rsync_port == 0 && (rsync_port = lp_rsync_port()) == 0) + rsync_port = RSYNC_PORT; + if (bind_address == NULL && *lp_bind_address()) + bind_address = lp_bind_address(); + + log_init(0); + + rprintf(FLOG, "rsyncd version %s starting, listening on port %d\n", + RSYNC_VERSION, rsync_port); + /* TODO: If listening on a particular address, then show that + * address too. In fact, why not just do getnameinfo on the + * local address??? */ + + start_accept_loop(rsync_port, start_daemon); + return -1; +} diff --git a/rsync/compat.c b/rsync/compat.c new file mode 100644 index 0000000..2454937 --- /dev/null +++ b/rsync/compat.c @@ -0,0 +1,336 @@ +/* + * Compatibility routines for older rsync protocol versions. + * + * Copyright (C) Andrew Tridgell 1996 + * Copyright (C) Paul Mackerras 1996 + * Copyright (C) 2004-2014 Wayne Davison + * + * 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, visit the http://fsf.org website. + */ + +#include "rsync.h" + +int remote_protocol = 0; +int file_extra_cnt = 0; /* count of file-list extras that everyone gets */ +int inc_recurse = 0; +int compat_flags = 0; +int use_safe_inc_flist = 0; +int want_xattr_optim = 0; + +extern int am_server; +extern int am_sender; +extern int local_server; +extern int inplace; +extern int recurse; +extern int use_qsort; +extern int allow_inc_recurse; +extern int preallocate_files; +extern int append_mode; +extern int fuzzy_basis; +extern int read_batch; +extern int delay_updates; +extern int checksum_seed; +extern int basis_dir_cnt; +extern int prune_empty_dirs; +extern int protocol_version; +extern int protect_args; +extern int preserve_uid; +extern int preserve_gid; +extern int preserve_acls; +extern int preserve_xattrs; +extern int need_messages_from_generator; +extern int delete_mode, delete_before, delete_during, delete_after; +extern char *shell_cmd; +extern char *partial_dir; +extern char *dest_option; +extern char *files_from; +extern char *filesfrom_host; +extern filter_rule_list filter_list; +extern int need_unsorted_flist; +#ifdef ICONV_OPTION +extern iconv_t ic_send, ic_recv; +extern char *iconv_opt; +#endif + +/* These index values are for the file-list's extra-attribute array. */ +int uid_ndx, gid_ndx, acls_ndx, xattrs_ndx, unsort_ndx; + +int receiver_symlink_times = 0; /* receiver can set the time on a symlink */ +int sender_symlink_iconv = 0; /* sender should convert symlink content */ + +#ifdef ICONV_OPTION +int filesfrom_convert = 0; +#endif + +#define CF_INC_RECURSE (1<<0) +#define CF_SYMLINK_TIMES (1<<1) +#define CF_SYMLINK_ICONV (1<<2) +#define CF_SAFE_FLIST (1<<3) +#define CF_AVOID_XATTR_OPTIM (1<<4) + +static const char *client_info; + +/* The server makes sure that if either side only supports a pre-release + * version of a protocol, that both sides must speak a compatible version + * of that protocol for it to be advertised as available. */ +static void check_sub_protocol(void) +{ + char *dot; + int their_protocol, their_sub; +#if SUBPROTOCOL_VERSION != 0 + int our_sub = protocol_version < PROTOCOL_VERSION ? 0 : SUBPROTOCOL_VERSION; +#else + int our_sub = 0; +#endif + + /* client_info starts with VER.SUB string if client is a pre-release. */ + if (!(their_protocol = atoi(client_info)) + || !(dot = strchr(client_info, '.')) + || !(their_sub = atoi(dot+1))) { +#if SUBPROTOCOL_VERSION != 0 + if (our_sub) + protocol_version--; +#endif + return; + } + + if (their_protocol < protocol_version) { + if (their_sub) + protocol_version = their_protocol - 1; + return; + } + + if (their_protocol > protocol_version) + their_sub = 0; /* 0 == final version of older protocol */ + if (their_sub != our_sub) + protocol_version--; +} + +void set_allow_inc_recurse(void) +{ + client_info = shell_cmd ? shell_cmd : ""; + + if (!recurse || use_qsort) + allow_inc_recurse = 0; + else if (!am_sender + && (delete_before || delete_after + || delay_updates || prune_empty_dirs)) + allow_inc_recurse = 0; + else if (am_server && !local_server + && (strchr(client_info, 'i') == NULL)) + allow_inc_recurse = 0; +} + +void setup_protocol(int f_out,int f_in) +{ + if (am_sender) + file_extra_cnt += PTR_EXTRA_CNT; + else + file_extra_cnt++; + if (preserve_uid) + uid_ndx = ++file_extra_cnt; + if (preserve_gid) + gid_ndx = ++file_extra_cnt; + if (preserve_acls && !am_sender) + acls_ndx = ++file_extra_cnt; + if (preserve_xattrs) + xattrs_ndx = ++file_extra_cnt; + + if (am_server) + set_allow_inc_recurse(); + + if (remote_protocol == 0) { + if (am_server && !local_server) + check_sub_protocol(); + if (!read_batch) + write_int(f_out, protocol_version); + remote_protocol = read_int(f_in); + if (protocol_version > remote_protocol) + protocol_version = remote_protocol; + } + if (read_batch && remote_protocol > protocol_version) { + rprintf(FERROR, "The protocol version in the batch file is too new (%d > %d).\n", + remote_protocol, protocol_version); + exit_cleanup(RERR_PROTOCOL); + } + + if (DEBUG_GTE(PROTO, 1)) { + rprintf(FINFO, "(%s) Protocol versions: remote=%d, negotiated=%d\n", + am_server? "Server" : "Client", remote_protocol, protocol_version); + } + if (remote_protocol < MIN_PROTOCOL_VERSION + || remote_protocol > MAX_PROTOCOL_VERSION) { + rprintf(FERROR,"protocol version mismatch -- is your shell clean?\n"); + rprintf(FERROR,"(see the rsync man page for an explanation)\n"); + exit_cleanup(RERR_PROTOCOL); + } + if (remote_protocol < OLD_PROTOCOL_VERSION) { + rprintf(FINFO,"%s is very old version of rsync, upgrade recommended.\n", + am_server? "Client" : "Server"); + } + if (protocol_version < MIN_PROTOCOL_VERSION) { + rprintf(FERROR, "--protocol must be at least %d on the %s.\n", + MIN_PROTOCOL_VERSION, am_server? "Server" : "Client"); + exit_cleanup(RERR_PROTOCOL); + } + if (protocol_version > PROTOCOL_VERSION) { + rprintf(FERROR, "--protocol must be no more than %d on the %s.\n", + PROTOCOL_VERSION, am_server? "Server" : "Client"); + exit_cleanup(RERR_PROTOCOL); + } + if (read_batch) + check_batch_flags(); + +#ifndef SUPPORT_PREALLOCATION + if (preallocate_files && !am_sender) { + rprintf(FERROR, "preallocation is not supported on this %s\n", + am_server ? "Server" : "Client"); + exit_cleanup(RERR_SYNTAX); + } +#endif + + if (protocol_version < 30) { + if (append_mode == 1) + append_mode = 2; + if (preserve_acls && !local_server) { + rprintf(FERROR, + "--acls requires protocol 30 or higher" + " (negotiated %d).\n", + protocol_version); + exit_cleanup(RERR_PROTOCOL); + } + if (preserve_xattrs && !local_server) { + rprintf(FERROR, + "--xattrs requires protocol 30 or higher" + " (negotiated %d).\n", + protocol_version); + exit_cleanup(RERR_PROTOCOL); + } + } + + if (delete_mode && !(delete_before+delete_during+delete_after)) { + if (protocol_version < 30) + delete_before = 1; + else + delete_during = 1; + } + + if (protocol_version < 29) { + if (fuzzy_basis) { + rprintf(FERROR, + "--fuzzy requires protocol 29 or higher" + " (negotiated %d).\n", + protocol_version); + exit_cleanup(RERR_PROTOCOL); + } + + if (basis_dir_cnt && inplace) { + rprintf(FERROR, + "%s with --inplace requires protocol 29 or higher" + " (negotiated %d).\n", + dest_option, protocol_version); + exit_cleanup(RERR_PROTOCOL); + } + + if (basis_dir_cnt > 1) { + rprintf(FERROR, + "Using more than one %s option requires protocol" + " 29 or higher (negotiated %d).\n", + dest_option, protocol_version); + exit_cleanup(RERR_PROTOCOL); + } + + if (prune_empty_dirs) { + rprintf(FERROR, + "--prune-empty-dirs requires protocol 29 or higher" + " (negotiated %d).\n", + protocol_version); + exit_cleanup(RERR_PROTOCOL); + } + } else if (protocol_version >= 30) { + if (am_server) { + compat_flags = allow_inc_recurse ? CF_INC_RECURSE : 0; +#ifdef CAN_SET_SYMLINK_TIMES + compat_flags |= CF_SYMLINK_TIMES; +#endif +#ifdef ICONV_OPTION + compat_flags |= CF_SYMLINK_ICONV; +#endif + if (local_server || strchr(client_info, 'f') != NULL) + compat_flags |= CF_SAFE_FLIST; + if (local_server || strchr(client_info, 'x') != NULL) + compat_flags |= CF_AVOID_XATTR_OPTIM; + write_byte(f_out, compat_flags); + } else + compat_flags = read_byte(f_in); + /* The inc_recurse var MUST be set to 0 or 1. */ + inc_recurse = compat_flags & CF_INC_RECURSE ? 1 : 0; + want_xattr_optim = protocol_version >= 31 && !(compat_flags & CF_AVOID_XATTR_OPTIM); + if (am_sender) { + receiver_symlink_times = am_server + ? strchr(client_info, 'L') != NULL + : !!(compat_flags & CF_SYMLINK_TIMES); + } +#ifdef CAN_SET_SYMLINK_TIMES + else + receiver_symlink_times = 1; +#endif +#ifdef ICONV_OPTION + sender_symlink_iconv = iconv_opt && (am_server + ? local_server || strchr(client_info, 's') != NULL + : !!(compat_flags & CF_SYMLINK_ICONV)); +#endif + if (inc_recurse && !allow_inc_recurse) { + /* This should only be able to happen in a batch. */ + fprintf(stderr, + "Incompatible options specified for inc-recursive %s.\n", + read_batch ? "batch file" : "connection"); + exit_cleanup(RERR_SYNTAX); + } + use_safe_inc_flist = (compat_flags & CF_SAFE_FLIST) || protocol_version >= 31; + need_messages_from_generator = 1; +#ifdef CAN_SET_SYMLINK_TIMES + } else if (!am_sender) { + receiver_symlink_times = 1; +#endif + } + + if (need_unsorted_flist && (!am_sender || inc_recurse)) + unsort_ndx = ++file_extra_cnt; + + if (partial_dir && *partial_dir != '/' && (!am_server || local_server)) { + int rflags = FILTRULE_NO_PREFIXES | FILTRULE_DIRECTORY; + if (!am_sender || protocol_version >= 30) + rflags |= FILTRULE_PERISHABLE; + parse_filter_str(&filter_list, partial_dir, rule_template(rflags), 0); + } + + +#ifdef ICONV_OPTION + if (protect_args && files_from) { + if (am_sender) + filesfrom_convert = filesfrom_host && ic_send != (iconv_t)-1; + else + filesfrom_convert = !filesfrom_host && ic_recv != (iconv_t)-1; + } +#endif + + if (am_server) { + if (!checksum_seed) + checksum_seed = time(NULL); + write_int(f_out, checksum_seed); + } else { + checksum_seed = read_int(f_in); + } +} diff --git a/rsync/config.h.in b/rsync/config.h.in new file mode 100644 index 0000000..7626273 --- /dev/null +++ b/rsync/config.h.in @@ -0,0 +1,769 @@ +/* config.h.in. Generated from configure.ac by autoheader. */ + +/* Define if building universal (internal helper macro) */ +#undef AC_APPLE_UNIVERSAL_BUILD + +/* Define to 1 if link() can hard-link special files. */ +#undef CAN_HARDLINK_SPECIAL + +/* Define to 1 if link() can hard-link symlinks. */ +#undef CAN_HARDLINK_SYMLINK + +/* Define to 1 if chown modifies symlinks. */ +#undef CHOWN_MODIFIES_SYMLINK + +/* Undefine if you do not want locale features. By default this is defined. */ +#undef CONFIG_LOCALE + +/* Define to one of `_getb67', `GETB67', `getb67' for Cray-2 and Cray-YMP + systems. This function is required for `alloca.c' support on those systems. + */ +#undef CRAY_STACKSEG_END + +/* Define to 1 if using `alloca.c'. */ +#undef C_ALLOCA + +/* Define to 1 if using external zlib */ +#undef EXTERNAL_ZLIB + +/* Used to make "checker" understand that FD_ZERO() clears memory. */ +#undef FORCE_FD_ZERO_MEMSET + +/* Define to the type of elements in the array set by `getgroups'. Usually + this is either `int' or `gid_t'. */ +#undef GETGROUPS_T + +/* Define to 1 if the `getpgrp' function requires zero arguments. */ +#undef GETPGRP_VOID + +/* Define to 1 if you have the `aclsort' function. */ +#undef HAVE_ACLSORT + +/* true if you have acl_get_perm_np */ +#undef HAVE_ACL_GET_PERM_NP + +/* Define to 1 if you have the header file. */ +#undef HAVE_ACL_LIBACL_H + +/* true if you have AIX ACLs */ +#undef HAVE_AIX_ACLS + +/* Define to 1 if you have `alloca', as a function or macro. */ +#undef HAVE_ALLOCA + +/* Define to 1 if you have and it should be used (not on Ultrix). + */ +#undef HAVE_ALLOCA_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_ARPA_INET_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_ARPA_NAMESER_H + +/* Define to 1 if you have the `asprintf' function. */ +#undef HAVE_ASPRINTF + +/* Define to 1 if you have the `attropen' function. */ +#undef HAVE_ATTROPEN + +/* Define to 1 if you have the header file. */ +#undef HAVE_ATTR_XATTR_H + +/* Define to 1 if readdir() is broken */ +#undef HAVE_BROKEN_READDIR + +/* Define to 1 if vsprintf has a C99-compatible return value */ +#undef HAVE_C99_VSNPRINTF + +/* Define to 1 if you have the `chmod' function. */ +#undef HAVE_CHMOD + +/* Define to 1 if you have the `chown' function. */ +#undef HAVE_CHOWN + +/* Define to 1 if you have the header file. */ +#undef HAVE_COMPAT_H + +/* Define to 1 if you have the "connect" function */ +#undef HAVE_CONNECT + +/* Define to 1 if you have the header file. */ +#undef HAVE_CTYPE_H + +/* Define to 1 if you have the header file, and it defines `DIR'. + */ +#undef HAVE_DIRENT_H + +/* Define if posix_fallocate is efficient (Cygwin) */ +#undef HAVE_EFFICIENT_POSIX_FALLOCATE + +/* Define to 1 if errno is declared in errno.h */ +#undef HAVE_ERRNO_DECL + +/* Define to 1 if you have the `extattr_get_link' function. */ +#undef HAVE_EXTATTR_GET_LINK + +/* Define to 1 if you have the fallocate function and it compiles and links + without error */ +#undef HAVE_FALLOCATE + +/* Define to 1 if you have the `fchmod' function. */ +#undef HAVE_FCHMOD + +/* Define to 1 if you have the header file. */ +#undef HAVE_FCNTL_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_FLOAT_H + +/* True if you have FreeBSD xattrs */ +#undef HAVE_FREEBSD_XATTRS + +/* Define to 1 if you have the `fstat' function. */ +#undef HAVE_FSTAT + +/* Define to 1 if you have the `ftruncate' function. */ +#undef HAVE_FTRUNCATE + +/* Define to 1 if you have the "getaddrinfo" function and required types. */ +#undef HAVE_GETADDRINFO + +/* Define to 1 if you have the `getcwd' function. */ +#undef HAVE_GETCWD + +/* Define to 1 if you have the `getegid' function. */ +#undef HAVE_GETEGID + +/* Define to 1 if you have the `geteuid' function. */ +#undef HAVE_GETEUID + +/* Define to 1 if you have the `getgrouplist' function. */ +#undef HAVE_GETGROUPLIST + +/* Define to 1 if you have the `getgroups' function. */ +#undef HAVE_GETGROUPS + +/* Define to 1 if you have the `getpass' function. */ +#undef HAVE_GETPASS + +/* Define to 1 if you have the `getpgrp' function. */ +#undef HAVE_GETPGRP + +/* Define to 1 if gettimeofday() takes a time-zone arg */ +#undef HAVE_GETTIMEOFDAY_TZ + +/* Define to 1 if you have the `getxattr' function. */ +#undef HAVE_GETXATTR + +/* Define to 1 if you have the header file. */ +#undef HAVE_GRP_H + +/* true if you have HPUX ACLs */ +#undef HAVE_HPUX_ACLS + +/* Define to 1 if you have the header file. */ +#undef HAVE_ICONV_H + +/* Define to 1 if you have the `iconv_open' function. */ +#undef HAVE_ICONV_OPEN + +/* Define to 1 if the system has the type `id_t'. */ +#undef HAVE_ID_T + +/* Define to 1 if you have the `inet_ntop' function. */ +#undef HAVE_INET_NTOP + +/* Define to 1 if you have the `inet_pton' function. */ +#undef HAVE_INET_PTON + +/* Define to 1 if you have the `initgroups' function. */ +#undef HAVE_INITGROUPS + +/* Define to 1 if you have the header file. */ +#undef HAVE_INTTYPES_H + +/* true if you have IRIX ACLs */ +#undef HAVE_IRIX_ACLS + +/* Define to 1 if you have the header file. */ +#undef HAVE_LANGINFO_H + +/* Define to 1 if you have the `lchmod' function. */ +#undef HAVE_LCHMOD + +/* Define to 1 if you have the `lchown' function. */ +#undef HAVE_LCHOWN + +/* Define to 1 if you have the `acl' library (-lacl). */ +#undef HAVE_LIBACL + +/* Define to 1 if you have the `attr' library (-lattr). */ +#undef HAVE_LIBATTR + +/* Define to 1 if you have the header file. */ +#undef HAVE_LIBCHARSET_H + +/* Define to 1 if you have the `inet' library (-linet). */ +#undef HAVE_LIBINET + +/* Define to 1 if you have the `nsl' library (-lnsl). */ +#undef HAVE_LIBNSL + +/* Define to 1 if you have the `nsl_s' library (-lnsl_s). */ +#undef HAVE_LIBNSL_S + +/* Define to 1 if you have the `popt' library (-lpopt). */ +#undef HAVE_LIBPOPT + +/* Define to 1 if you have the `resolv' library (-lresolv). */ +#undef HAVE_LIBRESOLV + +/* Define to 1 if you have the `sec' library (-lsec). */ +#undef HAVE_LIBSEC + +/* Define to 1 if you have the `socket' library (-lsocket). */ +#undef HAVE_LIBSOCKET + +/* Define to 1 if you have the `z' library (-lz). */ +#undef HAVE_LIBZ + +/* Define to 1 if you have the header file. */ +#undef HAVE_LIMITS_H + +/* Define to 1 if you have the `link' function. */ +#undef HAVE_LINK + +/* Define to 1 if you have the header file. */ +#undef HAVE_LINUX_FALLOC_H + +/* True if you have Linux xattrs */ +#undef HAVE_LINUX_XATTRS + +/* Define to 1 if you have the `locale_charset' function. */ +#undef HAVE_LOCALE_CHARSET + +/* Define to 1 if you have the header file. */ +#undef HAVE_LOCALE_H + +/* Define to 1 if the type `long double' works and has more range or precision + than `double'. */ +#undef HAVE_LONG_DOUBLE + +/* Define to 1 if the type `long double' works and has more range or precision + than `double'. */ +#undef HAVE_LONG_DOUBLE_WIDER + +/* Define to 1 if you have the `lseek64' function. */ +#undef HAVE_LSEEK64 + +/* Define to 1 if you have the `lutimes' function. */ +#undef HAVE_LUTIMES + +/* Define to 1 if you have the `mallinfo' function. */ +#undef HAVE_MALLINFO + +/* Define to 1 if you have the header file. */ +#undef HAVE_MALLOC_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_MCHECK_H + +/* Define to 1 if you have the `memmove' function. */ +#undef HAVE_MEMMOVE + +/* Define to 1 if you have the header file. */ +#undef HAVE_MEMORY_H + +/* Define to 1 if you have the `mkfifo' function. */ +#undef HAVE_MKFIFO + +/* Define to 1 if you have the `mknod' function. */ +#undef HAVE_MKNOD + +/* Define to 1 if you have the `mkstemp64' function. */ +#undef HAVE_MKSTEMP64 + +/* Define to 1 if the system has the type `mode_t'. */ +#undef HAVE_MODE_T + +/* Define to 1 if you have the `mtrace' function. */ +#undef HAVE_MTRACE + +/* Define to 1 if you have the header file, and it defines `DIR'. */ +#undef HAVE_NDIR_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_NETDB_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_NETINET_IN_SYSTM_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_NETINET_IP_H + +/* Define to 1 if you have the `nl_langinfo' function. */ +#undef HAVE_NL_LANGINFO + +/* Define to 1 if the system has the type `off_t'. */ +#undef HAVE_OFF_T + +/* Define to 1 if you have the `open64' function. */ +#undef HAVE_OPEN64 + +/* true if you have Mac OS X ACLs */ +#undef HAVE_OSX_ACLS + +/* True if you have Mac OS X xattrs */ +#undef HAVE_OSX_XATTRS + +/* Define to 1 if the system has the type `pid_t'. */ +#undef HAVE_PID_T + +/* Define to 1 if you have the header file. */ +#undef HAVE_POPT_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_POPT_POPT_H + +/* true if you have posix ACLs */ +#undef HAVE_POSIX_ACLS + +/* Define to 1 if you have the `posix_fallocate' function. */ +#undef HAVE_POSIX_FALLOCATE + +/* Define to 1 if you have the `putenv' function. */ +#undef HAVE_PUTENV + +/* Define to 1 if you have the `readlink' function. */ +#undef HAVE_READLINK + +/* Define to 1 if remote shell is remsh, not rsh */ +#undef HAVE_REMSH + +/* Define to 1 if mkstemp() is available and works right */ +#undef HAVE_SECURE_MKSTEMP + +/* Define to 1 if you have the `setattrlist' function. */ +#undef HAVE_SETATTRLIST + +/* Define to 1 if you have the `seteuid' function. */ +#undef HAVE_SETEUID + +/* Define to 1 if you have the `setgroups' function. */ +#undef HAVE_SETGROUPS + +/* Define to 1 if you have the `setlocale' function. */ +#undef HAVE_SETLOCALE + +/* Define to 1 if you have the `setmode' function. */ +#undef HAVE_SETMODE + +/* Define to 1 if you have the `setsid' function. */ +#undef HAVE_SETSID + +/* Define to 1 if you have the `setvbuf' function. */ +#undef HAVE_SETVBUF + +/* Define to 1 if you have the `sigaction' function. */ +#undef HAVE_SIGACTION + +/* Define to 1 if you have the `sigprocmask' function. */ +#undef HAVE_SIGPROCMASK + +/* Define to 1 if the system has the type `size_t'. */ +#undef HAVE_SIZE_T + +/* Define to 1 if you have the `snprintf' function. */ +#undef HAVE_SNPRINTF + +/* Do we have sockaddr_in6.sin6_scope_id? */ +#undef HAVE_SOCKADDR_IN6_SCOPE_ID + +/* Do we have sockaddr_in.sin_len? */ +#undef HAVE_SOCKADDR_IN_LEN + +/* Do we have sockaddr.sa_len? */ +#undef HAVE_SOCKADDR_LEN + +/* Do we have sockaddr_un.sun_len? */ +#undef HAVE_SOCKADDR_UN_LEN + +/* Define to 1 if you have the "socketpair" function */ +#undef HAVE_SOCKETPAIR + +/* true if you have solaris ACLs */ +#undef HAVE_SOLARIS_ACLS + +/* True if you have Solaris xattrs */ +#undef HAVE_SOLARIS_XATTRS + +/* Define to 1 if you have the header file. */ +#undef HAVE_STDINT_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_STDLIB_H + +/* Define to 1 if you have the `strcasecmp' function. */ +#undef HAVE_STRCASECMP + +/* Define to 1 if you have the `strchr' function. */ +#undef HAVE_STRCHR + +/* Define to 1 if you have the `strdup' function. */ +#undef HAVE_STRDUP + +/* Define to 1 if you have the `strerror' function. */ +#undef HAVE_STRERROR + +/* Define to 1 if you have the `strftime' function. */ +#undef HAVE_STRFTIME + +/* Define to 1 if you have the header file. */ +#undef HAVE_STRINGS_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_STRING_H + +/* Define to 1 if you have the `strlcat' function. */ +#undef HAVE_STRLCAT + +/* Define to 1 if you have the `strlcpy' function. */ +#undef HAVE_STRLCPY + +/* Define to 1 if you have the `strpbrk' function. */ +#undef HAVE_STRPBRK + +/* Define to 1 if you have the `strtol' function. */ +#undef HAVE_STRTOL + +/* Define to 1 if the system has the type `struct addrinfo'. */ +#undef HAVE_STRUCT_ADDRINFO + +/* Define to 1 if the system has the type `struct sockaddr_storage'. */ +#undef HAVE_STRUCT_SOCKADDR_STORAGE + +/* Define to 1 if the system has the type `struct stat64'. */ +#undef HAVE_STRUCT_STAT64 + +/* Define to 1 if `st_mtimensec' is a member of `struct stat'. */ +#undef HAVE_STRUCT_STAT_ST_MTIMENSEC + +/* Define to 1 if `st_mtim.tv_nsec' is a member of `struct stat'. */ +#undef HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC + +/* Define to 1 if `st_rdev' is a member of `struct stat'. */ +#undef HAVE_STRUCT_STAT_ST_RDEV + +/* Define to 1 if you have the "struct utimbuf" type */ +#undef HAVE_STRUCT_UTIMBUF + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_ACL_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_ATTR_H + +/* Define to 1 if you have the header file, and it defines `DIR'. + */ +#undef HAVE_SYS_DIR_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_EXTATTR_H + +/* Define to 1 if you have the SYS_fallocate syscall number */ +#undef HAVE_SYS_FALLOCATE + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_FCNTL_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_FILIO_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_IOCTL_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_MODE_H + +/* Define to 1 if you have the header file, and it defines `DIR'. + */ +#undef HAVE_SYS_NDIR_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_PARAM_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_SELECT_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_SOCKET_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_STAT_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_TIME_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_TYPES_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_UNISTD_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_UN_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_WAIT_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_XATTR_H + +/* Define to 1 if you have the `tcgetpgrp' function. */ +#undef HAVE_TCGETPGRP + +/* true if you have Tru64 ACLs */ +#undef HAVE_TRU64_ACLS + +/* Define to 1 if you have the header file. */ +#undef HAVE_UNISTD_H + +/* true if you have UnixWare ACLs */ +#undef HAVE_UNIXWARE_ACLS + +/* Define to 1 if you have the `utime' function. */ +#undef HAVE_UTIME + +/* Define to 1 if you have the `utimensat' function. */ +#undef HAVE_UTIMENSAT + +/* Define to 1 if you have the `utimes' function. */ +#undef HAVE_UTIMES + +/* Define to 1 if you have the header file. */ +#undef HAVE_UTIME_H + +/* Define to 1 if `utime(file, NULL)' sets file's timestamp to the present. */ +#undef HAVE_UTIME_NULL + +/* Define to 1 if you have the `vasprintf' function. */ +#undef HAVE_VASPRINTF + +/* Define to 1 if you have the `va_copy' function. */ +#undef HAVE_VA_COPY + +/* Define to 1 if you have the `vsnprintf' function. */ +#undef HAVE_VSNPRINTF + +/* Define to 1 if you have the `wait4' function. */ +#undef HAVE_WAIT4 + +/* Define to 1 if you have the `waitpid' function. */ +#undef HAVE_WAITPID + +/* Define to 1 if you have the header file. */ +#undef HAVE_ZLIB_H + +/* Define to 1 if you have the `_acl' function. */ +#undef HAVE__ACL + +/* Define to 1 if you have the `_facl' function. */ +#undef HAVE__FACL + +/* Define to 1 if you have the `__acl' function. */ +#undef HAVE___ACL + +/* Define to 1 if you have the `__facl' function. */ +#undef HAVE___FACL + +/* Define to 1 if you have the `__va_copy' function. */ +#undef HAVE___VA_COPY + +/* Define as const if the declaration of iconv() needs const. */ +#undef ICONV_CONST + +/* Define if you want the --iconv option. Specifing a value will set the + default iconv setting (a NULL means no --iconv processing by default). */ +#undef ICONV_OPTION + +/* true if you have IPv6 */ +#undef INET6 + +/* Define to 1 if `major', `minor', and `makedev' are declared in . + */ +#undef MAJOR_IN_MKDEV + +/* Define to 1 if `major', `minor', and `makedev' are declared in + . */ +#undef MAJOR_IN_SYSMACROS + +/* Define to 1 if makedev() takes 3 args */ +#undef MAKEDEV_TAKES_3_ARGS + +/* Define to 1 if mknod() can create FIFOs. */ +#undef MKNOD_CREATES_FIFOS + +/* Define to 1 if mknod() can create sockets. */ +#undef MKNOD_CREATES_SOCKETS + +/* unprivileged group for unprivileged user */ +#undef NOBODY_GROUP + +/* unprivileged user--e.g. nobody */ +#undef NOBODY_USER + +/* True if device files do not support xattrs */ +#undef NO_DEVICE_XATTRS + +/* True if special files do not support xattrs */ +#undef NO_SPECIAL_XATTRS + +/* True if symlinks do not support user xattrs */ +#undef NO_SYMLINK_USER_XATTRS + +/* True if symlinks do not support xattrs */ +#undef NO_SYMLINK_XATTRS + +/* Define to the address where bug reports for this package should be sent. */ +#undef PACKAGE_BUGREPORT + +/* Define to the full name of this package. */ +#undef PACKAGE_NAME + +/* Define to the full name and version of this package. */ +#undef PACKAGE_STRING + +/* Define to the one symbol short name of this package. */ +#undef PACKAGE_TARNAME + +/* Define to the home page for this package. */ +#undef PACKAGE_URL + +/* Define to the version of this package. */ +#undef PACKAGE_VERSION + +/* Define as the return type of signal handlers (`int' or `void'). */ +#undef RETSIGTYPE + +/* location of configuration file for rsync server */ +#undef RSYNCD_SYSCONF + +/* location of rsync on remote machine */ +#undef RSYNC_PATH + +/* default -e command */ +#undef RSYNC_RSH + +/* Define to 1 if --protected-args should be the default */ +#undef RSYNC_USE_PROTECTED_ARGS + +/* rsync release version */ +#undef RSYNC_VERSION + +/* Define to 1 if sockets need to be shutdown */ +#undef SHUTDOWN_ALL_SOCKETS + +/* Define to 1 if "signed char" is a valid type */ +#undef SIGNED_CHAR_OK + +/* The size of `int', as computed by sizeof. */ +#undef SIZEOF_INT + +/* The size of `int16_t', as computed by sizeof. */ +#undef SIZEOF_INT16_T + +/* The size of `int32_t', as computed by sizeof. */ +#undef SIZEOF_INT32_T + +/* The size of `int64_t', as computed by sizeof. */ +#undef SIZEOF_INT64_T + +/* The size of `long', as computed by sizeof. */ +#undef SIZEOF_LONG + +/* The size of `long long', as computed by sizeof. */ +#undef SIZEOF_LONG_LONG + +/* The size of `off64_t', as computed by sizeof. */ +#undef SIZEOF_OFF64_T + +/* The size of `off_t', as computed by sizeof. */ +#undef SIZEOF_OFF_T + +/* The size of `short', as computed by sizeof. */ +#undef SIZEOF_SHORT + +/* The size of `time_t', as computed by sizeof. */ +#undef SIZEOF_TIME_T + +/* The size of `uint16_t', as computed by sizeof. */ +#undef SIZEOF_UINT16_T + +/* The size of `uint32_t', as computed by sizeof. */ +#undef SIZEOF_UINT32_T + +/* If using the C implementation of alloca, define if you know the + direction of stack growth for your system; otherwise it will be + automatically deduced at runtime. + STACK_DIRECTION > 0 => grows toward higher addresses + STACK_DIRECTION < 0 => grows toward lower addresses + STACK_DIRECTION = 0 => direction of growth unknown */ +#undef STACK_DIRECTION + +/* Define to 1 if you have the ANSI C header files. */ +#undef STDC_HEADERS + +/* Define to 1 to add support for ACLs */ +#undef SUPPORT_ACLS + +/* Define to 1 to add support for extended attributes */ +#undef SUPPORT_XATTRS + +/* Define to 1 if you can safely include both and . */ +#undef TIME_WITH_SYS_TIME + +/* Define to 1 if you want rsync to make use of iconv_open() */ +#undef USE_ICONV_OPEN + +/* String to pass to iconv() for the UTF-8 charset. */ +#undef UTF8_CHARSET + +/* Define WORDS_BIGENDIAN to 1 if your processor stores words with the most + significant byte first (like Motorola and SPARC, unlike Intel). */ +#if defined AC_APPLE_UNIVERSAL_BUILD +# if defined __BIG_ENDIAN__ +# define WORDS_BIGENDIAN 1 +# endif +#else +# ifndef WORDS_BIGENDIAN +# undef WORDS_BIGENDIAN +# endif +#endif + +/* Number of bits in a file offset, on hosts where this is settable. */ +#undef _FILE_OFFSET_BITS + +/* Define _GNU_SOURCE so that we get all necessary prototypes */ +#undef _GNU_SOURCE + +/* Define for large files, on AIX-style hosts. */ +#undef _LARGE_FILES + +/* Define to `int' if doesn't define. */ +#undef gid_t + +/* Define to `__inline__' or `__inline' if that's what the C compiler + calls it, or to nothing if 'inline' is not supported under any name. */ +#ifndef __cplusplus +#undef inline +#endif + +/* Define to `unsigned int' if does not define. */ +#undef size_t + +/* type to use in place of socklen_t if not defined */ +#undef socklen_t + +/* Define to `int' if doesn't define. */ +#undef uid_t diff --git a/rsync/connection.c b/rsync/connection.c new file mode 100644 index 0000000..6281861 --- /dev/null +++ b/rsync/connection.c @@ -0,0 +1,46 @@ +/* + * Support the max connections option. + * + * Copyright (C) 1998 Andrew Tridgell + * + * 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, visit the http://fsf.org website. + */ + +#include "rsync.h" + +/* A simple routine to do connection counting. This returns 1 on success + * and 0 on failure, with errno also being set if the open() failed (errno + * will be 0 if the lock request failed). */ +int claim_connection(char *fname, int max_connections) +{ + int fd, i; + + if (max_connections == 0) + return 1; + + if ((fd = open(fname, O_RDWR|O_CREAT, 0600)) < 0) + return 0; + + /* Find a free spot. */ + for (i = 0; i < max_connections; i++) { + if (lock_range(fd, i*4, 4)) + return 1; + } + + close(fd); + + /* A lock failure needs to return an errno of 0. */ + errno = 0; + return 0; +} diff --git a/rsync/csprotocol.txt b/rsync/csprotocol.txt new file mode 100644 index 0000000..c8dadd4 --- /dev/null +++ b/rsync/csprotocol.txt @@ -0,0 +1,88 @@ +This is kind of informal and may be wrong, but it helped me. It's +basically a summary of clientserver.c and authenticate.c. + + -- Martin Pool + + +This is the protocol used for rsync --daemon; i.e. connections to port +873 rather than invocations over a remote shell. + +When the server accepts a connection, it prints a greeting + + @RSYNCD: . + +where is the numeric version (see PROTOCOL_VERSION in rsync.h) +'.' is a literal period, and is the numeric subprotocol +version (see SUBPROTOCOL_VERSION -- it will be 0 for final releases). +Protocols prior to 30 only output alone. The daemon expects +to see a similar greeting back from the client. For protocols prior to +30, an absent "." value is assumed to be 0. For protocol +30, an absent value is a fatal error. The daemon then follows this line +with a free-format text message-of-the-day (if any is defined). + +The server is now in the connected state. The client can either send +the command + + #list + +to get a listing of modules, or the name of a module. After this, the +connection is now bound to a particular module. Access per host for +this module is now checked, as is per-module connection limits. + +If authentication is required to use this module, the server will say + + @RSYNCD: AUTHREQD + +where is a random string of base64 characters. The client +must respond with + + + +where is the username they claim to be, and is the +base64 form of the MD4 hash of challenge+password. + +At this point the server applies all remaining constraints before +handing control to the client, including switching uid/gid, setting up +include and exclude lists, moving to the root of the module, and doing +chroot. + +If the login is acceptable, then the server will respond with + + @RSYNCD: OK + +The client now writes some rsync options, as if it were remotely +executing the command. The server parses these arguments as if it had +just been invoked with them, but they're added to the existing state. +So if the client specifies a list of files to be included or excluded, +they'll defer to existing limits specified in the server +configuration. + +At this point the client and server both switch to using a +multiplexing layer across the socket. The main point of this is to +allow the server to asynchronously pass errors back, while still +allowing streamed and pipelined data. + +Unfortunately, the multiplex protocol is not used at every stage. We +start up in plain socket mode and then change over by calling +io_start_buffering. Of course both the client and the server have to +do this at the same point. + +The server then talks to the client as normal across the socket, +passing checksums, file lists and so on. For documentation of that, +stay tuned (or write it yourself!). + + + +------------ +Protocol version changes + +30 (2007-10-04, 3.0.0pre1) + + The use of a "." number was added to + @RSYNCD: . + +25 (2001-08-20, 2.4.7pre2) + + Send an explicit "@RSYNC EXIT" command at the end of the + module listing. We never intentionally end the transmission + by just closing the socket anymore. diff --git a/rsync/delete.c b/rsync/delete.c new file mode 100644 index 0000000..2927a93 --- /dev/null +++ b/rsync/delete.c @@ -0,0 +1,240 @@ +/* + * Deletion routines used in rsync. + * + * Copyright (C) 1996-2000 Andrew Tridgell + * Copyright (C) 1996 Paul Mackerras + * Copyright (C) 2002 Martin Pool + * Copyright (C) 2003-2014 Wayne Davison + * + * 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, visit the http://fsf.org website. + */ + +#include "rsync.h" + +extern int am_root; +extern int make_backups; +extern int max_delete; +extern char *backup_dir; +extern char *backup_suffix; +extern int backup_suffix_len; +extern struct stats stats; + +int ignore_perishable = 0; +int non_perishable_cnt = 0; +int skipped_deletes = 0; + +static inline int is_backup_file(char *fn) +{ + int k = strlen(fn) - backup_suffix_len; + return k > 0 && strcmp(fn+k, backup_suffix) == 0; +} + +/* The directory is about to be deleted: if DEL_RECURSE is given, delete all + * its contents, otherwise just checks for content. Returns DR_SUCCESS or + * DR_NOT_EMPTY. Note that fname must point to a MAXPATHLEN buffer! (The + * buffer is used for recursion, but returned unchanged.) + */ +static enum delret delete_dir_contents(char *fname, uint16 flags) +{ + struct file_list *dirlist; + enum delret ret; + unsigned remainder; + void *save_filters; + int j, dlen; + char *p; + + if (DEBUG_GTE(DEL, 3)) { + rprintf(FINFO, "delete_dir_contents(%s) flags=%d\n", + fname, flags); + } + + dlen = strlen(fname); + save_filters = push_local_filters(fname, dlen); + + non_perishable_cnt = 0; + dirlist = get_dirlist(fname, dlen, 0); + ret = non_perishable_cnt ? DR_NOT_EMPTY : DR_SUCCESS; + + if (!dirlist->used) + goto done; + + if (!(flags & DEL_RECURSE)) { + ret = DR_NOT_EMPTY; + goto done; + } + + p = fname + dlen; + if (dlen != 1 || *fname != '/') + *p++ = '/'; + remainder = MAXPATHLEN - (p - fname); + + /* We do our own recursion, so make delete_item() non-recursive. */ + flags = (flags & ~(DEL_RECURSE|DEL_MAKE_ROOM|DEL_NO_UID_WRITE)) + | DEL_DIR_IS_EMPTY; + + for (j = dirlist->used; j--; ) { + struct file_struct *fp = dirlist->files[j]; + + if (fp->flags & FLAG_MOUNT_DIR && S_ISDIR(fp->mode)) { + if (DEBUG_GTE(DEL, 1)) { + rprintf(FINFO, + "mount point, %s, pins parent directory\n", + f_name(fp, NULL)); + } + ret = DR_NOT_EMPTY; + continue; + } + + strlcpy(p, fp->basename, remainder); + if (!(fp->mode & S_IWUSR) && !am_root && fp->flags & FLAG_OWNED_BY_US) + do_chmod(fname, fp->mode | S_IWUSR); + /* Save stack by recursing to ourself directly. */ + if (S_ISDIR(fp->mode)) { + if (delete_dir_contents(fname, flags | DEL_RECURSE) != DR_SUCCESS) + ret = DR_NOT_EMPTY; + } + if (delete_item(fname, fp->mode, flags) != DR_SUCCESS) + ret = DR_NOT_EMPTY; + } + + fname[dlen] = '\0'; + + done: + flist_free(dirlist); + pop_local_filters(save_filters); + + if (ret == DR_NOT_EMPTY) { + rprintf(FINFO, "cannot delete non-empty directory: %s\n", + fname); + } + return ret; +} + +/* Delete a file or directory. If DEL_RECURSE is set in the flags, this will + * delete recursively. + * + * Note that fbuf must point to a MAXPATHLEN buffer if the mode indicates it's + * a directory! (The buffer is used for recursion, but returned unchanged.) + */ +enum delret delete_item(char *fbuf, uint16 mode, uint16 flags) +{ + enum delret ret; + char *what; + int ok; + + if (DEBUG_GTE(DEL, 2)) { + rprintf(FINFO, "delete_item(%s) mode=%o flags=%d\n", + fbuf, (int)mode, (int)flags); + } + + if (flags & DEL_NO_UID_WRITE) + do_chmod(fbuf, mode | S_IWUSR); + + if (S_ISDIR(mode) && !(flags & DEL_DIR_IS_EMPTY)) { + /* This only happens on the first call to delete_item() since + * delete_dir_contents() always calls us w/DEL_DIR_IS_EMPTY. */ + ignore_perishable = 1; + /* If DEL_RECURSE is not set, this just reports emptiness. */ + ret = delete_dir_contents(fbuf, flags); + ignore_perishable = 0; + if (ret == DR_NOT_EMPTY || ret == DR_AT_LIMIT) + goto check_ret; + /* OK: try to delete the directory. */ + } + + if (!(flags & DEL_MAKE_ROOM) && max_delete >= 0 && stats.deleted_files >= max_delete) { + skipped_deletes++; + return DR_AT_LIMIT; + } + + if (S_ISDIR(mode)) { + what = "rmdir"; + ok = do_rmdir(fbuf) == 0; + } else { + if (make_backups > 0 && !(flags & DEL_FOR_BACKUP) && (backup_dir || !is_backup_file(fbuf))) { + what = "make_backup"; + ok = make_backup(fbuf, True); + if (ok == 2) { + what = "unlink"; + ok = robust_unlink(fbuf) == 0; + } + } else { + what = "unlink"; + ok = robust_unlink(fbuf) == 0; + } + } + + if (ok) { + if (!(flags & DEL_MAKE_ROOM)) { + log_delete(fbuf, mode); + stats.deleted_files++; + if (S_ISREG(mode)) { + /* Nothing more to count */ + } else if (S_ISDIR(mode)) + stats.deleted_dirs++; +#ifdef SUPPORT_LINKS + else if (S_ISLNK(mode)) + stats.deleted_symlinks++; +#endif + else if (IS_DEVICE(mode)) + stats.deleted_symlinks++; + else + stats.deleted_specials++; + } + ret = DR_SUCCESS; + } else { + if (S_ISDIR(mode) && errno == ENOTEMPTY) { + rprintf(FINFO, "cannot delete non-empty directory: %s\n", + fbuf); + ret = DR_NOT_EMPTY; + } else if (errno != ENOENT) { + rsyserr(FERROR_XFER, errno, "delete_file: %s(%s) failed", + what, fbuf); + ret = DR_FAILURE; + } else + ret = DR_SUCCESS; + } + + check_ret: + if (ret != DR_SUCCESS && flags & DEL_MAKE_ROOM) { + const char *desc; + switch (flags & DEL_MAKE_ROOM) { + case DEL_FOR_FILE: desc = "regular file"; break; + case DEL_FOR_DIR: desc = "directory"; break; + case DEL_FOR_SYMLINK: desc = "symlink"; break; + case DEL_FOR_DEVICE: desc = "device file"; break; + case DEL_FOR_SPECIAL: desc = "special file"; break; + default: exit_cleanup(RERR_UNSUPPORTED); /* IMPOSSIBLE */ + } + rprintf(FERROR_XFER, "could not make way for %s %s: %s\n", + flags & DEL_FOR_BACKUP ? "backup" : "new", + desc, fbuf); + } + return ret; +} + +uint16 get_del_for_flag(uint16 mode) +{ + if (S_ISREG(mode)) + return DEL_FOR_FILE; + if (S_ISDIR(mode)) + return DEL_FOR_DIR; + if (S_ISLNK(mode)) + return DEL_FOR_SYMLINK; + if (IS_DEVICE(mode)) + return DEL_FOR_DEVICE; + if (IS_SPECIAL(mode)) + return DEL_FOR_SPECIAL; + exit_cleanup(RERR_UNSUPPORTED); /* IMPOSSIBLE */ +} diff --git a/rsync/doc/README-SGML b/rsync/doc/README-SGML new file mode 100644 index 0000000..ce5a8e1 --- /dev/null +++ b/rsync/doc/README-SGML @@ -0,0 +1,20 @@ +Handling the rsync SGML documentation + +rsync documentation is now primarily in Docbook format. Docbook is an +SGML/XML documentation format that is becoming standard on free +operating systems. It's also used for Samba documentation. + +The SGML files are source code that can be translated into various +useful output formats, primarily PDF, HTML, Postscript and plain text. + +To do this transformation on Debian, you should install the +docbook-utils package. Having done that, you can say + + docbook2pdf rsync.sgml + +and so on. + +On other systems you probably need James Clark's "sp" and "JadeTeX" +packages. Work it out for yourself and send a note to the mailing +list. + diff --git a/rsync/doc/profile.txt b/rsync/doc/profile.txt new file mode 100644 index 0000000..b911d0a --- /dev/null +++ b/rsync/doc/profile.txt @@ -0,0 +1,42 @@ +Notes on rsync profiling + +strlcpy is hot: + + 0.00 0.00 1/7735635 push_dir [68] + 0.00 0.00 1/7735635 pop_dir [71] + 0.00 0.00 1/7735635 send_file_list [15] + 0.01 0.00 18857/7735635 send_files [4] + 0.04 0.00 129260/7735635 send_file_entry [18] + 0.04 0.00 129260/7735635 make_file [20] + 0.04 0.00 141666/7735635 send_directory [36] + 2.29 0.00 7316589/7735635 f_name [13] +[14] 11.7 2.42 0.00 7735635 strlcpy [14] + + +Here's the top few functions: + + 46.23 9.57 9.57 13160929 0.00 0.00 mdfour64 + 14.78 12.63 3.06 13160929 0.00 0.00 copy64 + 11.69 15.05 2.42 7735635 0.00 0.00 strlcpy + 10.05 17.13 2.08 41438 0.05 0.38 sum_update + 4.11 17.98 0.85 13159996 0.00 0.00 mdfour_update + 1.50 18.29 0.31 file_compare + 1.45 18.59 0.30 129261 0.00 0.01 send_file_entry + 1.23 18.84 0.26 2557585 0.00 0.00 f_name + 1.11 19.07 0.23 1483750 0.00 0.00 u_strcmp + 1.11 19.30 0.23 118129 0.00 0.00 writefd_unbuffered + 0.92 19.50 0.19 1085011 0.00 0.00 writefd + 0.43 19.59 0.09 156987 0.00 0.00 read_timeout + 0.43 19.68 0.09 129261 0.00 0.00 clean_fname + 0.39 19.75 0.08 32887 0.00 0.38 matched + 0.34 19.82 0.07 1 70.00 16293.92 send_files + 0.29 19.89 0.06 129260 0.00 0.00 make_file + 0.29 19.95 0.06 75430 0.00 0.00 read_unbuffered + + + +mdfour could perhaps be made faster: + +/* NOTE: This code makes no attempt to be fast! */ + +There might be an optimized version somewhere that we can borrow. diff --git a/rsync/doc/rsync.sgml b/rsync/doc/rsync.sgml new file mode 100644 index 0000000..76a50c2 --- /dev/null +++ b/rsync/doc/rsync.sgml @@ -0,0 +1,351 @@ + + + + rsync + + 1996 -- 2002 + Martin Pool + Andrew Tridgell + + + Martin + Pool + + + + + Introduction + + rsync is a flexible program for efficiently copying files or + directory trees. + + rsync has many options to select which files will be copied + and how they are to be transferred. It may be used as an + alternative to ftp, http, scp or rcp. + + The rsync remote-update protocol allows rsync to transfer just + the differences between two sets of files across the network link, + using an efficient checksum-search algorithm described in the + technical report that accompanies this package. + + Some of the additional features of rsync are: + + + + + support for copying links, devices, owners, groups and + permissions + + + + + + exclude and exclude-from options similar to GNU tar + + + + + + a CVS exclude mode for ignoring the same files that CVS would ignore + + + + + can use any transparent remote shell, including rsh or ssh + + + + + does not require root privileges + + + + + pipelining of file transfers to minimize latency costs + + + + + support for anonymous or authenticated rsync servers (ideal for + mirroring) + + + + + + + + + Using rsync +
+ + Introductory example + + + + Probably the most common case of rsync usage is to copy files + to or from a remote machine using + ssh as a network transport. In + this situation rsync is a good alternative to + scp. + + + + The most commonly used arguments for rsync are + + + + + + + Be verbose. Primarily, display the name of each file as it is copied. + + + + + + + + + Reproduce the structure and attributes of the origin files as exactly + as possible: this includes copying subdirectories, symlinks, special + files, ownership and permissions. (@xref{Attributes to + copy}.) + + + + + + + + + + + Compress network traffic, using a modified version of the + @command{zlib} library. + + + Display a progress indicator while files are transferred. This should + normally be ommitted if rsync is not run on a terminal. + +
+ + + + +
+ Local and remote + + There are six different ways of using rsync. They + are: + + + + + + + + for copying local files. This is invoked when neither + source nor destination path contains a @code{:} separator + + + + for copying from the local machine to a remote machine using + a remote shell program as the transport (such as rsh or + ssh). This is invoked when the destination path contains a + single @code{:} separator. + + + + for copying from a remote machine to the local machine + using a remote shell program. This is invoked when the source + contains a @code{:} separator. + + + + for copying from a remote rsync server to the local + machine. This is invoked when the source path contains a @code{::} + separator or a @code{rsync://} URL. + + + + for copying from the local machine to a remote rsync + server. This is invoked when the destination path contains a @code{::} + separator. + + + + for listing files on a remote machine. This is done the + same way as rsync transfers except that you leave off the + local destination. + + + + +Note that in all cases (other than listing) at least one of the source +and destination paths must be local. + + +Any one invocation of rsync makes a copy in a single direction. rsync +currently has no equivalent of @command{ftp}'s interactive mode. + +@cindex @sc{nfs} +@cindex network filesystems +@cindex remote filesystems + + +rsync's network protocol is generally faster at copying files than +network filesystems such as @sc{nfs} or @sc{cifs}. It is better to +run rsync on the file server either as a daemon or over ssh than +running rsync giving the network directory. + +
+
+ + + + + Frequently asked questions + + + + + + + + + + Are there mailing lists for rsync? + + + + Yes, and you can subscribe and unsubscribe through a + web interface at + http://lists.samba.org/ + + + + If you are having trouble with the mailing list, please + send mail to the administrator + + rsync-admin@lists.samba.org + + not to the list itself. + + + + The mailing list archives are searchable. Use + Google and prepend + the search with site:lists.samba.org + rsync, plus relevant keywords. + + + + + + + + + Why is rsync so much bigger when I build it with + gcc? + + + + + On gcc, rsync builds by default with debug symbols + included. If you strip both executables, they should end + up about the same size. (Use make + install-strip.) + + + + + + + + Is rsync useful for a single large file like an ISO image? + + + + Yes, but note the following: + + + Background: A common use of rsync is to update a file (or set of files) in one location from a more + correct or up-to-date copy in another location, taking advantage of portions of the files that are + identical to speed up the process. (Note that rsync will transfer a file in its entirety if no copy + exists at the destination.) + + + (This discussion is written in terms of updating a local copy of a file from a correct file in a + remote location, although rsync can work in either direction.) + + + The file to be updated (the local file) must be in a destination directory that has enough space for + two copies of the file. (In addition, keep an extra copy of the file to be updated in a different + location for safety -- see the discussion (below) about rsync's behavior when the rsync process is + interrupted before completion.) + + + The local file must have the same name as the remote file being sync'd to (I think?). If you are + trying to upgrade an iso from, for example, beta1 to beta2, rename the local file to the same name + as the beta2 file. *(This is a useful thing to do -- only the changed portions will be + transmitted.)* + + + The extra copy of the local file kept in a different location is because of rsync's behavior if + interrupted before completion: + + + * If you specify the --partial option and rsync is interrupted, rsync will save the partially + rsync'd file and throw away the original local copy. (The partially rsync'd file is correct but + truncated.) If rsync is restarted, it will not have a local copy of the file to check for duplicate + blocks beyond the section of the file that has already been rsync'd, thus the remainder of the rsync + process will be a "pure transfer" of the file rather than taking advantage of the rsync algorithm. + + + * If you don't specify the --partial option and rsync is interrupted, rsync will throw away the + partially rsync'd file, and, when rsync is restarted starts the rsync process over from the + beginning. + + + Which of these is most desirable depends on the degree of commonality between the local and remote + copies of the file *and how much progress was made before the interruption*. + + + The ideal approach after an interruption would be to create a new file by taking the original file + and deleting a portion equal in size to the portion already rsync'd and then appending *the + remaining* portion to the portion of the file that has already been rsync'd. (There has been some + discussion about creating an option to do this automatically.) + + The --compare-dest option is useful when transferring multiple files, but is of no benefit in + transferring a single file. (AFAIK) + + *Other potentially useful information can be found at: + -[3]http://twiki.org/cgi-bin/view/Wikilearn/RsyncingALargeFile + + This answer, formatted with "real" bullets, can be found at: + -[4]http://twiki.org/cgi-bin/view/Wikilearn/RsyncingALargeFileFAQ* + + + + + + + + + + Other Resources + + + +
\ No newline at end of file diff --git a/rsync/errcode.h b/rsync/errcode.h new file mode 100644 index 0000000..a428b89 --- /dev/null +++ b/rsync/errcode.h @@ -0,0 +1,64 @@ +/* + * Error codes returned by rsync. + * + * Copyright (C) 1998-2000 Andrew Tridgell + * Copyright (C) 2003-2014 Wayne Davison + * + * 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, visit the http://fsf.org website. + */ + +/* If you change these, please also update the string mappings in log.c and + * the EXIT VALUES in rsync.yo. */ + +#define RERR_OK 0 +#define RERR_SYNTAX 1 /* syntax or usage error */ +#define RERR_PROTOCOL 2 /* protocol incompatibility */ +#define RERR_FILESELECT 3 /* errors selecting input/output files, dirs */ +#define RERR_UNSUPPORTED 4 /* requested action not supported */ +#define RERR_STARTCLIENT 5 /* error starting client-server protocol */ + +#define RERR_SOCKETIO 10 /* error in socket IO */ +#define RERR_FILEIO 11 /* error in file IO */ +#define RERR_STREAMIO 12 /* error in rsync protocol data stream */ +#define RERR_MESSAGEIO 13 /* errors with program diagnostics */ +#define RERR_IPC 14 /* error in IPC code */ +#define RERR_CRASHED 15 /* sibling crashed */ +#define RERR_TERMINATED 16 /* sibling terminated abnormally */ + +#define RERR_SIGNAL1 19 /* status returned when sent SIGUSR1 */ +#define RERR_SIGNAL 20 /* status returned when sent SIGINT, SIGTERM, SIGHUP */ +#define RERR_WAITCHILD 21 /* some error returned by waitpid() */ +#define RERR_MALLOC 22 /* error allocating core memory buffers */ +#define RERR_PARTIAL 23 /* partial transfer */ +#define RERR_VANISHED 24 /* file(s) vanished on sender side */ +#define RERR_DEL_LIMIT 25 /* skipped some deletes due to --max-delete */ + +#define RERR_TIMEOUT 30 /* timeout in data send/receive */ +#define RERR_CONTIMEOUT 35 /* timeout waiting for daemon connection */ + +/* Although it doesn't seem to be specified anywhere, + * ssh and the shell seem to return these values: + * + * 124 if the command exited with status 255 + * 125 if the command is killed by a signal + * 126 if the command cannot be run + * 127 if the command is not found + * + * and we could use this to give a better explanation if the remote + * command is not found. + */ +#define RERR_CMD_FAILED 124 +#define RERR_CMD_KILLED 125 +#define RERR_CMD_RUN 126 +#define RERR_CMD_NOTFOUND 127 diff --git a/rsync/exclude.c b/rsync/exclude.c new file mode 100644 index 0000000..efa5b48 --- /dev/null +++ b/rsync/exclude.c @@ -0,0 +1,1401 @@ +/* + * The filter include/exclude routines. + * + * Copyright (C) 1996-2001 Andrew Tridgell + * Copyright (C) 1996 Paul Mackerras + * Copyright (C) 2002 Martin Pool + * Copyright (C) 2003-2014 Wayne Davison + * + * 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, visit the http://fsf.org website. + */ + +#include "rsync.h" + +extern int am_server; +extern int am_sender; +extern int eol_nulls; +extern int io_error; +extern int local_server; +extern int prune_empty_dirs; +extern int ignore_perishable; +extern int delete_mode; +extern int delete_excluded; +extern int cvs_exclude; +extern int sanitize_paths; +extern int protocol_version; +extern int module_id; + +extern char curr_dir[MAXPATHLEN]; +extern unsigned int curr_dir_len; +extern unsigned int module_dirlen; + +filter_rule_list filter_list = { .debug_type = "" }; +filter_rule_list cvs_filter_list = { .debug_type = " [global CVS]" }; +filter_rule_list daemon_filter_list = { .debug_type = " [daemon]" }; + +/* Need room enough for ":MODS " prefix plus some room to grow. */ +#define MAX_RULE_PREFIX (16) + +#define SLASH_WILD3_SUFFIX "/***" + +/* The dirbuf is set by push_local_filters() to the current subdirectory + * relative to curr_dir that is being processed. The path always has a + * trailing slash appended, and the variable dirbuf_len contains the length + * of this path prefix. The path is always absolute. */ +static char dirbuf[MAXPATHLEN+1]; +static unsigned int dirbuf_len = 0; +static int dirbuf_depth; + +/* This is True when we're scanning parent dirs for per-dir merge-files. */ +static BOOL parent_dirscan = False; + +/* This array contains a list of all the currently active per-dir merge + * files. This makes it easier to save the appropriate values when we + * "push" down into each subdirectory. */ +static filter_rule **mergelist_parents; +static int mergelist_cnt = 0; +static int mergelist_size = 0; + +/* Each filter_list_struct describes a singly-linked list by keeping track + * of both the head and tail pointers. The list is slightly unusual in that + * a parent-dir's content can be appended to the end of the local list in a + * special way: the last item in the local list has its "next" pointer set + * to point to the inherited list, but the local list's tail pointer points + * at the end of the local list. Thus, if the local list is empty, the head + * will be pointing at the inherited content but the tail will be NULL. To + * help you visualize this, here are the possible list arrangements: + * + * Completely Empty Local Content Only + * ================================== ==================================== + * head -> NULL head -> Local1 -> Local2 -> NULL + * tail -> NULL tail -------------^ + * + * Inherited Content Only Both Local and Inherited Content + * ================================== ==================================== + * head -> Parent1 -> Parent2 -> NULL head -> L1 -> L2 -> P1 -> P2 -> NULL + * tail -> NULL tail ---------^ + * + * This means that anyone wanting to traverse the whole list to use it just + * needs to start at the head and use the "next" pointers until it goes + * NULL. To add new local content, we insert the item after the tail item + * and update the tail (obviously, if "tail" was NULL, we insert it at the + * head). To clear the local list, WE MUST NOT FREE THE INHERITED CONTENT + * because it is shared between the current list and our parent list(s). + * The easiest way to handle this is to simply truncate the list after the + * tail item and then free the local list from the head. When inheriting + * the list for a new local dir, we just save off the filter_list_struct + * values (so we can pop back to them later) and set the tail to NULL. + */ + +static void teardown_mergelist(filter_rule *ex) +{ + if (DEBUG_GTE(FILTER, 2)) { + rprintf(FINFO, "[%s] deactivating mergelist #%d%s\n", + who_am_i(), mergelist_cnt - 1, + ex->u.mergelist->debug_type); + } + + /* We should deactivate mergelists in LIFO order. */ + assert(mergelist_cnt > 0); + assert(ex == mergelist_parents[mergelist_cnt - 1]); + + /* The parent_dirscan filters should have been freed. */ + assert(ex->u.mergelist->parent_dirscan_head == NULL); + + free(ex->u.mergelist->debug_type); + free(ex->u.mergelist); + mergelist_cnt--; +} + +static void free_filter(filter_rule *ex) +{ + free(ex->pattern); + free(ex); +} + +static void free_filters(filter_rule *head) +{ + filter_rule *rev_head = NULL; + + /* Reverse the list so we deactivate mergelists in the proper LIFO + * order. */ + while (head) { + filter_rule *next = head->next; + head->next = rev_head; + rev_head = head; + head = next; + } + + while (rev_head) { + filter_rule *prev = rev_head->next; + /* Tear down mergelists here, not in free_filter, so that we + * affect only real filter lists and not temporarily allocated + * filters. */ + if (rev_head->rflags & FILTRULE_PERDIR_MERGE) + teardown_mergelist(rev_head); + free_filter(rev_head); + rev_head = prev; + } +} + +/* Build a filter structure given a filter pattern. The value in "pat" + * is not null-terminated. "rule" is either held or freed, so the + * caller should not free it. */ +static void add_rule(filter_rule_list *listp, const char *pat, unsigned int pat_len, + filter_rule *rule, int xflags) +{ + const char *cp; + unsigned int pre_len, suf_len, slash_cnt = 0; + + if (DEBUG_GTE(FILTER, 2)) { + rprintf(FINFO, "[%s] add_rule(%s%.*s%s)%s\n", + who_am_i(), get_rule_prefix(rule, pat, 0, NULL), + (int)pat_len, pat, + (rule->rflags & FILTRULE_DIRECTORY) ? "/" : "", + listp->debug_type); + } + + /* These flags also indicate that we're reading a list that + * needs to be filtered now, not post-filtered later. */ + if (xflags & (XFLG_ANCHORED2ABS|XFLG_ABS_IF_SLASH) + && (rule->rflags & FILTRULES_SIDES) + == (am_sender ? FILTRULE_RECEIVER_SIDE : FILTRULE_SENDER_SIDE)) { + /* This filter applies only to the other side. Drop it. */ + free_filter(rule); + return; + } + + if (pat_len > 1 && pat[pat_len-1] == '/') { + pat_len--; + rule->rflags |= FILTRULE_DIRECTORY; + } + + for (cp = pat; cp < pat + pat_len; cp++) { + if (*cp == '/') + slash_cnt++; + } + + if (!(rule->rflags & (FILTRULE_ABS_PATH | FILTRULE_MERGE_FILE)) + && ((xflags & (XFLG_ANCHORED2ABS|XFLG_ABS_IF_SLASH) && *pat == '/') + || (xflags & XFLG_ABS_IF_SLASH && slash_cnt))) { + rule->rflags |= FILTRULE_ABS_PATH; + if (*pat == '/') + pre_len = dirbuf_len - module_dirlen - 1; + else + pre_len = 0; + } else + pre_len = 0; + + /* The daemon wants dir-exclude rules to get an appended "/" + "***". */ + if (xflags & XFLG_DIR2WILD3 + && BITS_SETnUNSET(rule->rflags, FILTRULE_DIRECTORY, FILTRULE_INCLUDE)) { + rule->rflags &= ~FILTRULE_DIRECTORY; + suf_len = sizeof SLASH_WILD3_SUFFIX - 1; + } else + suf_len = 0; + + if (!(rule->pattern = new_array(char, pre_len + pat_len + suf_len + 1))) + out_of_memory("add_rule"); + if (pre_len) { + memcpy(rule->pattern, dirbuf + module_dirlen, pre_len); + for (cp = rule->pattern; cp < rule->pattern + pre_len; cp++) { + if (*cp == '/') + slash_cnt++; + } + } + strlcpy(rule->pattern + pre_len, pat, pat_len + 1); + pat_len += pre_len; + if (suf_len) { + memcpy(rule->pattern + pat_len, SLASH_WILD3_SUFFIX, suf_len+1); + pat_len += suf_len; + slash_cnt++; + } + + if (strpbrk(rule->pattern, "*[?")) { + rule->rflags |= FILTRULE_WILD; + if ((cp = strstr(rule->pattern, "**")) != NULL) { + rule->rflags |= FILTRULE_WILD2; + /* If the pattern starts with **, note that. */ + if (cp == rule->pattern) + rule->rflags |= FILTRULE_WILD2_PREFIX; + /* If the pattern ends with ***, note that. */ + if (pat_len >= 3 + && rule->pattern[pat_len-3] == '*' + && rule->pattern[pat_len-2] == '*' + && rule->pattern[pat_len-1] == '*') + rule->rflags |= FILTRULE_WILD3_SUFFIX; + } + } + + if (rule->rflags & FILTRULE_PERDIR_MERGE) { + filter_rule_list *lp; + unsigned int len; + int i; + + if ((cp = strrchr(rule->pattern, '/')) != NULL) + cp++; + else + cp = rule->pattern; + + /* If the local merge file was already mentioned, don't + * add it again. */ + for (i = 0; i < mergelist_cnt; i++) { + filter_rule *ex = mergelist_parents[i]; + const char *s = strrchr(ex->pattern, '/'); + if (s) + s++; + else + s = ex->pattern; + len = strlen(s); + if (len == pat_len - (cp - rule->pattern) && memcmp(s, cp, len) == 0) { + free_filter(rule); + return; + } + } + + if (!(lp = new_array(filter_rule_list, 1))) + out_of_memory("add_rule"); + lp->head = lp->tail = lp->parent_dirscan_head = NULL; + if (asprintf(&lp->debug_type, " [per-dir %s]", cp) < 0) + out_of_memory("add_rule"); + rule->u.mergelist = lp; + + if (mergelist_cnt == mergelist_size) { + mergelist_size += 5; + mergelist_parents = realloc_array(mergelist_parents, + filter_rule *, + mergelist_size); + if (!mergelist_parents) + out_of_memory("add_rule"); + } + if (DEBUG_GTE(FILTER, 2)) { + rprintf(FINFO, "[%s] activating mergelist #%d%s\n", + who_am_i(), mergelist_cnt, lp->debug_type); + } + mergelist_parents[mergelist_cnt++] = rule; + } else + rule->u.slash_cnt = slash_cnt; + + if (!listp->tail) { + rule->next = listp->head; + listp->head = listp->tail = rule; + } else { + rule->next = listp->tail->next; + listp->tail->next = rule; + listp->tail = rule; + } +} + +static void clear_filter_list(filter_rule_list *listp) +{ + if (listp->tail) { + /* Truncate any inherited items from the local list. */ + listp->tail->next = NULL; + /* Now free everything that is left. */ + free_filters(listp->head); + } + + listp->head = listp->tail = NULL; +} + +/* This returns an expanded (absolute) filename for the merge-file name if + * the name has any slashes in it OR if the parent_dirscan var is True; + * otherwise it returns the original merge_file name. If the len_ptr value + * is non-NULL the merge_file name is limited by the referenced length + * value and will be updated with the length of the resulting name. We + * always return a name that is null terminated, even if the merge_file + * name was not. */ +static char *parse_merge_name(const char *merge_file, unsigned int *len_ptr, + unsigned int prefix_skip) +{ + static char buf[MAXPATHLEN]; + char *fn, tmpbuf[MAXPATHLEN]; + unsigned int fn_len; + + if (!parent_dirscan && *merge_file != '/') { + /* Return the name unchanged it doesn't have any slashes. */ + if (len_ptr) { + const char *p = merge_file + *len_ptr; + while (--p > merge_file && *p != '/') {} + if (p == merge_file) { + strlcpy(buf, merge_file, *len_ptr + 1); + return buf; + } + } else if (strchr(merge_file, '/') == NULL) + return (char *)merge_file; + } + + fn = *merge_file == '/' ? buf : tmpbuf; + if (sanitize_paths) { + const char *r = prefix_skip ? "/" : NULL; + /* null-terminate the name if it isn't already */ + if (len_ptr && merge_file[*len_ptr]) { + char *to = fn == buf ? tmpbuf : buf; + strlcpy(to, merge_file, *len_ptr + 1); + merge_file = to; + } + if (!sanitize_path(fn, merge_file, r, dirbuf_depth, SP_DEFAULT)) { + rprintf(FERROR, "merge-file name overflows: %s\n", + merge_file); + return NULL; + } + fn_len = strlen(fn); + } else { + strlcpy(fn, merge_file, len_ptr ? *len_ptr + 1 : MAXPATHLEN); + fn_len = clean_fname(fn, CFN_COLLAPSE_DOT_DOT_DIRS); + } + + /* If the name isn't in buf yet, it wasn't absolute. */ + if (fn != buf) { + int d_len = dirbuf_len - prefix_skip; + if (d_len + fn_len >= MAXPATHLEN) { + rprintf(FERROR, "merge-file name overflows: %s\n", fn); + return NULL; + } + memcpy(buf, dirbuf + prefix_skip, d_len); + memcpy(buf + d_len, fn, fn_len + 1); + fn_len = clean_fname(buf, CFN_COLLAPSE_DOT_DOT_DIRS); + } + + if (len_ptr) + *len_ptr = fn_len; + return buf; +} + +/* Sets the dirbuf and dirbuf_len values. */ +void set_filter_dir(const char *dir, unsigned int dirlen) +{ + unsigned int len; + if (*dir != '/') { + memcpy(dirbuf, curr_dir, curr_dir_len); + dirbuf[curr_dir_len] = '/'; + len = curr_dir_len + 1; + if (len + dirlen >= MAXPATHLEN) + dirlen = 0; + } else + len = 0; + memcpy(dirbuf + len, dir, dirlen); + dirbuf[dirlen + len] = '\0'; + dirbuf_len = clean_fname(dirbuf, CFN_COLLAPSE_DOT_DOT_DIRS); + if (dirbuf_len > 1 && dirbuf[dirbuf_len-1] == '.' + && dirbuf[dirbuf_len-2] == '/') + dirbuf_len -= 2; + if (dirbuf_len != 1) + dirbuf[dirbuf_len++] = '/'; + dirbuf[dirbuf_len] = '\0'; + if (sanitize_paths) + dirbuf_depth = count_dir_elements(dirbuf + module_dirlen); +} + +/* This routine takes a per-dir merge-file entry and finishes its setup. + * If the name has a path portion then we check to see if it refers to a + * parent directory of the first transfer dir. If it does, we scan all the + * dirs from that point through the parent dir of the transfer dir looking + * for the per-dir merge-file in each one. */ +static BOOL setup_merge_file(int mergelist_num, filter_rule *ex, + filter_rule_list *lp) +{ + char buf[MAXPATHLEN]; + char *x, *y, *pat = ex->pattern; + unsigned int len; + + if (!(x = parse_merge_name(pat, NULL, 0)) || *x != '/') + return 0; + + if (DEBUG_GTE(FILTER, 2)) { + rprintf(FINFO, "[%s] performing parent_dirscan for mergelist #%d%s\n", + who_am_i(), mergelist_num, lp->debug_type); + } + y = strrchr(x, '/'); + *y = '\0'; + ex->pattern = strdup(y+1); + if (!*x) + x = "/"; + if (*x == '/') + strlcpy(buf, x, MAXPATHLEN); + else + pathjoin(buf, MAXPATHLEN, dirbuf, x); + + len = clean_fname(buf, CFN_COLLAPSE_DOT_DOT_DIRS); + if (len != 1 && len < MAXPATHLEN-1) { + buf[len++] = '/'; + buf[len] = '\0'; + } + /* This ensures that the specified dir is a parent of the transfer. */ + for (x = buf, y = dirbuf; *x && *x == *y; x++, y++) {} + if (*x) + y += strlen(y); /* nope -- skip the scan */ + + parent_dirscan = True; + while (*y) { + char save[MAXPATHLEN]; + strlcpy(save, y, MAXPATHLEN); + *y = '\0'; + dirbuf_len = y - dirbuf; + strlcpy(x, ex->pattern, MAXPATHLEN - (x - buf)); + parse_filter_file(lp, buf, ex, XFLG_ANCHORED2ABS); + if (ex->rflags & FILTRULE_NO_INHERIT) { + /* Free the undesired rules to clean up any per-dir + * mergelists they defined. Otherwise pop_local_filters + * may crash trying to restore nonexistent state for + * those mergelists. */ + free_filters(lp->head); + lp->head = NULL; + } + lp->tail = NULL; + strlcpy(y, save, MAXPATHLEN); + while ((*x++ = *y++) != '/') {} + } + /* Save current head for freeing when the mergelist becomes inactive. */ + lp->parent_dirscan_head = lp->head; + parent_dirscan = False; + if (DEBUG_GTE(FILTER, 2)) { + rprintf(FINFO, "[%s] completed parent_dirscan for mergelist #%d%s\n", + who_am_i(), mergelist_num, lp->debug_type); + } + free(pat); + return 1; +} + +struct local_filter_state { + int mergelist_cnt; + filter_rule_list mergelists[1]; +}; + +/* Each time rsync changes to a new directory it call this function to + * handle all the per-dir merge-files. The "dir" value is the current path + * relative to curr_dir (which might not be null-terminated). We copy it + * into dirbuf so that we can easily append a file name on the end. */ +void *push_local_filters(const char *dir, unsigned int dirlen) +{ + struct local_filter_state *push; + int i; + + set_filter_dir(dir, dirlen); + if (DEBUG_GTE(FILTER, 2)) { + rprintf(FINFO, "[%s] pushing local filters for %s\n", + who_am_i(), dirbuf); + } + + if (!mergelist_cnt) { + /* No old state to save and no new merge files to push. */ + return NULL; + } + + push = (struct local_filter_state *)new_array(char, + sizeof (struct local_filter_state) + + (mergelist_cnt-1) * sizeof (filter_rule_list)); + if (!push) + out_of_memory("push_local_filters"); + + push->mergelist_cnt = mergelist_cnt; + for (i = 0; i < mergelist_cnt; i++) { + memcpy(&push->mergelists[i], mergelist_parents[i]->u.mergelist, + sizeof (filter_rule_list)); + } + + /* Note: parse_filter_file() might increase mergelist_cnt, so keep + * this loop separate from the above loop. */ + for (i = 0; i < mergelist_cnt; i++) { + filter_rule *ex = mergelist_parents[i]; + filter_rule_list *lp = ex->u.mergelist; + + if (DEBUG_GTE(FILTER, 2)) { + rprintf(FINFO, "[%s] pushing mergelist #%d%s\n", + who_am_i(), i, lp->debug_type); + } + + lp->tail = NULL; /* Switch any local rules to inherited. */ + if (ex->rflags & FILTRULE_NO_INHERIT) + lp->head = NULL; + + if (ex->rflags & FILTRULE_FINISH_SETUP) { + ex->rflags &= ~FILTRULE_FINISH_SETUP; + if (setup_merge_file(i, ex, lp)) + set_filter_dir(dir, dirlen); + } + + if (strlcpy(dirbuf + dirbuf_len, ex->pattern, + MAXPATHLEN - dirbuf_len) < MAXPATHLEN - dirbuf_len) { + parse_filter_file(lp, dirbuf, ex, + XFLG_ANCHORED2ABS); + } else { + io_error |= IOERR_GENERAL; + rprintf(FERROR, + "cannot add local filter rules in long-named directory: %s\n", + full_fname(dirbuf)); + } + dirbuf[dirbuf_len] = '\0'; + } + + return (void*)push; +} + +void pop_local_filters(void *mem) +{ + struct local_filter_state *pop = (struct local_filter_state *)mem; + int i; + int old_mergelist_cnt = pop ? pop->mergelist_cnt : 0; + + if (DEBUG_GTE(FILTER, 2)) + rprintf(FINFO, "[%s] popping local filters\n", who_am_i()); + + for (i = mergelist_cnt; i-- > 0; ) { + filter_rule *ex = mergelist_parents[i]; + filter_rule_list *lp = ex->u.mergelist; + + if (DEBUG_GTE(FILTER, 2)) { + rprintf(FINFO, "[%s] popping mergelist #%d%s\n", + who_am_i(), i, lp->debug_type); + } + + clear_filter_list(lp); + + if (i >= old_mergelist_cnt) { + /* This mergelist does not exist in the state to be + * restored. Free its parent_dirscan list to clean up + * any per-dir mergelists defined there so we don't + * crash trying to restore nonexistent state for them + * below. (Counterpart to setup_merge_file call in + * push_local_filters. Must be done here, not in + * free_filter, for LIFO order.) */ + if (DEBUG_GTE(FILTER, 2)) { + rprintf(FINFO, "[%s] freeing parent_dirscan filters of mergelist #%d%s\n", + who_am_i(), i, ex->u.mergelist->debug_type); + } + free_filters(lp->parent_dirscan_head); + lp->parent_dirscan_head = NULL; + } + } + + /* If we cleaned things up properly, the only still-active mergelists + * should be those with a state to be restored. */ + assert(mergelist_cnt == old_mergelist_cnt); + + if (!pop) { + /* No state to restore. */ + return; + } + + for (i = 0; i < mergelist_cnt; i++) { + memcpy(mergelist_parents[i]->u.mergelist, &pop->mergelists[i], + sizeof (filter_rule_list)); + } + + free(pop); +} + +void change_local_filter_dir(const char *dname, int dlen, int dir_depth) +{ + static int cur_depth = -1; + static void *filt_array[MAXPATHLEN/2+1]; + + if (!dname) { + for ( ; cur_depth >= 0; cur_depth--) { + if (filt_array[cur_depth]) { + pop_local_filters(filt_array[cur_depth]); + filt_array[cur_depth] = NULL; + } + } + return; + } + + assert(dir_depth < MAXPATHLEN/2+1); + + for ( ; cur_depth >= dir_depth; cur_depth--) { + if (filt_array[cur_depth]) { + pop_local_filters(filt_array[cur_depth]); + filt_array[cur_depth] = NULL; + } + } + + cur_depth = dir_depth; + filt_array[cur_depth] = push_local_filters(dname, dlen); +} + +static int rule_matches(const char *fname, filter_rule *ex, int name_is_dir) +{ + int slash_handling, str_cnt = 0, anchored_match = 0; + int ret_match = ex->rflags & FILTRULE_NEGATE ? 0 : 1; + char *p, *pattern = ex->pattern; + const char *strings[16]; /* more than enough */ + const char *name = fname + (*fname == '/'); + + if (!*name) + return 0; + + if (!ex->u.slash_cnt && !(ex->rflags & FILTRULE_WILD2)) { + /* If the pattern does not have any slashes AND it does + * not have a "**" (which could match a slash), then we + * just match the name portion of the path. */ + if ((p = strrchr(name,'/')) != NULL) + name = p+1; + } else if (ex->rflags & FILTRULE_ABS_PATH && *fname != '/' + && curr_dir_len > module_dirlen + 1) { + /* If we're matching against an absolute-path pattern, + * we need to prepend our full path info. */ + strings[str_cnt++] = curr_dir + module_dirlen + 1; + strings[str_cnt++] = "/"; + } else if (ex->rflags & FILTRULE_WILD2_PREFIX && *fname != '/') { + /* Allow "**"+"/" to match at the start of the string. */ + strings[str_cnt++] = "/"; + } + strings[str_cnt++] = name; + if (name_is_dir) { + /* Allow a trailing "/"+"***" to match the directory. */ + if (ex->rflags & FILTRULE_WILD3_SUFFIX) + strings[str_cnt++] = "/"; + } else if (ex->rflags & FILTRULE_DIRECTORY) + return !ret_match; + strings[str_cnt] = NULL; + + if (*pattern == '/') { + anchored_match = 1; + pattern++; + } + + if (!anchored_match && ex->u.slash_cnt + && !(ex->rflags & FILTRULE_WILD2)) { + /* A non-anchored match with an infix slash and no "**" + * needs to match the last slash_cnt+1 name elements. */ + slash_handling = ex->u.slash_cnt + 1; + } else if (!anchored_match && !(ex->rflags & FILTRULE_WILD2_PREFIX) + && ex->rflags & FILTRULE_WILD2) { + /* A non-anchored match with an infix or trailing "**" (but not + * a prefixed "**") needs to try matching after every slash. */ + slash_handling = -1; + } else { + /* The pattern matches only at the start of the path or name. */ + slash_handling = 0; + } + + if (ex->rflags & FILTRULE_WILD) { + if (wildmatch_array(pattern, strings, slash_handling)) + return ret_match; + } else if (str_cnt > 1) { + if (litmatch_array(pattern, strings, slash_handling)) + return ret_match; + } else if (anchored_match) { + if (strcmp(name, pattern) == 0) + return ret_match; + } else { + int l1 = strlen(name); + int l2 = strlen(pattern); + if (l2 <= l1 && + strcmp(name+(l1-l2),pattern) == 0 && + (l1==l2 || name[l1-(l2+1)] == '/')) { + return ret_match; + } + } + + return !ret_match; +} + +static void report_filter_result(enum logcode code, char const *name, + filter_rule const *ent, + int name_is_dir, const char *type) +{ + /* If a trailing slash is present to match only directories, + * then it is stripped out by add_rule(). So as a special + * case we add it back in here. */ + + if (DEBUG_GTE(FILTER, 1)) { + static char *actions[2][2] + = { {"show", "hid"}, {"risk", "protect"} }; + const char *w = who_am_i(); + rprintf(code, "[%s] %sing %s %s because of pattern %s%s%s\n", + w, actions[*w!='s'][!(ent->rflags & FILTRULE_INCLUDE)], + name_is_dir ? "directory" : "file", name, ent->pattern, + ent->rflags & FILTRULE_DIRECTORY ? "/" : "", type); + } +} + +/* Return -1 if file "name" is defined to be excluded by the specified + * exclude list, 1 if it is included, and 0 if it was not matched. */ +int check_filter(filter_rule_list *listp, enum logcode code, + const char *name, int name_is_dir) +{ + filter_rule *ent; + + for (ent = listp->head; ent; ent = ent->next) { + if (ignore_perishable && ent->rflags & FILTRULE_PERISHABLE) + continue; + if (ent->rflags & FILTRULE_PERDIR_MERGE) { + int rc = check_filter(ent->u.mergelist, code, name, + name_is_dir); + if (rc) + return rc; + continue; + } + if (ent->rflags & FILTRULE_CVS_IGNORE) { + int rc = check_filter(&cvs_filter_list, code, name, + name_is_dir); + if (rc) + return rc; + continue; + } + if (rule_matches(name, ent, name_is_dir)) { + report_filter_result(code, name, ent, name_is_dir, + listp->debug_type); + return ent->rflags & FILTRULE_INCLUDE ? 1 : -1; + } + } + + return 0; +} + +#define RULE_STRCMP(s,r) rule_strcmp((s), (r), sizeof (r) - 1) + +static const uchar *rule_strcmp(const uchar *str, const char *rule, int rule_len) +{ + if (strncmp((char*)str, rule, rule_len) != 0) + return NULL; + if (isspace(str[rule_len]) || str[rule_len] == '_' || !str[rule_len]) + return str + rule_len - 1; + if (str[rule_len] == ',') + return str + rule_len; + return NULL; +} + +#define FILTRULES_FROM_CONTAINER (FILTRULE_ABS_PATH | FILTRULE_INCLUDE \ + | FILTRULE_DIRECTORY | FILTRULE_NEGATE \ + | FILTRULE_PERISHABLE) + +/* Gets the next include/exclude rule from *rulestr_ptr and advances + * *rulestr_ptr to point beyond it. Stores the pattern's start (within + * *rulestr_ptr) and length in *pat_ptr and *pat_len_ptr, and returns a newly + * allocated filter_rule containing the rest of the information. Returns + * NULL if there are no more rules in the input. + * + * The template provides defaults for the new rule to inherit, and the + * template rflags and the xflags additionally affect parsing. */ +static filter_rule *parse_rule_tok(const char **rulestr_ptr, + const filter_rule *template, int xflags, + const char **pat_ptr, unsigned int *pat_len_ptr) +{ + const uchar *s = (const uchar *)*rulestr_ptr; + filter_rule *rule; + unsigned int len; + + if (template->rflags & FILTRULE_WORD_SPLIT) { + /* Skip over any initial whitespace. */ + while (isspace(*s)) + s++; + /* Update to point to real start of rule. */ + *rulestr_ptr = (const char *)s; + } + if (!*s) + return NULL; + + if (!(rule = new0(filter_rule))) + out_of_memory("parse_rule_tok"); + + /* Inherit from the template. Don't inherit FILTRULES_SIDES; we check + * that later. */ + rule->rflags = template->rflags & FILTRULES_FROM_CONTAINER; + + /* Figure out what kind of a filter rule "s" is pointing at. Note + * that if FILTRULE_NO_PREFIXES is set, the rule is either an include + * or an exclude based on the inheritance of the FILTRULE_INCLUDE + * flag (above). XFLG_OLD_PREFIXES indicates a compatibility mode + * for old include/exclude patterns where just "+ " and "- " are + * allowed as optional prefixes. */ + if (template->rflags & FILTRULE_NO_PREFIXES) { + if (*s == '!' && template->rflags & FILTRULE_CVS_IGNORE) + rule->rflags |= FILTRULE_CLEAR_LIST; /* Tentative! */ + } else if (xflags & XFLG_OLD_PREFIXES) { + if (*s == '-' && s[1] == ' ') { + rule->rflags &= ~FILTRULE_INCLUDE; + s += 2; + } else if (*s == '+' && s[1] == ' ') { + rule->rflags |= FILTRULE_INCLUDE; + s += 2; + } else if (*s == '!') + rule->rflags |= FILTRULE_CLEAR_LIST; /* Tentative! */ + } else { + char ch = 0; + BOOL prefix_specifies_side = False; + switch (*s) { + case 'c': + if ((s = RULE_STRCMP(s, "clear")) != NULL) + ch = '!'; + break; + case 'd': + if ((s = RULE_STRCMP(s, "dir-merge")) != NULL) + ch = ':'; + break; + case 'e': + if ((s = RULE_STRCMP(s, "exclude")) != NULL) + ch = '-'; + break; + case 'h': + if ((s = RULE_STRCMP(s, "hide")) != NULL) + ch = 'H'; + break; + case 'i': + if ((s = RULE_STRCMP(s, "include")) != NULL) + ch = '+'; + break; + case 'm': + if ((s = RULE_STRCMP(s, "merge")) != NULL) + ch = '.'; + break; + case 'p': + if ((s = RULE_STRCMP(s, "protect")) != NULL) + ch = 'P'; + break; + case 'r': + if ((s = RULE_STRCMP(s, "risk")) != NULL) + ch = 'R'; + break; + case 's': + if ((s = RULE_STRCMP(s, "show")) != NULL) + ch = 'S'; + break; + default: + ch = *s; + if (s[1] == ',') + s++; + break; + } + switch (ch) { + case ':': + rule->rflags |= FILTRULE_PERDIR_MERGE + | FILTRULE_FINISH_SETUP; + /* FALL THROUGH */ + case '.': + rule->rflags |= FILTRULE_MERGE_FILE; + break; + case '+': + rule->rflags |= FILTRULE_INCLUDE; + break; + case '-': + break; + case 'S': + rule->rflags |= FILTRULE_INCLUDE; + /* FALL THROUGH */ + case 'H': + rule->rflags |= FILTRULE_SENDER_SIDE; + prefix_specifies_side = True; + break; + case 'R': + rule->rflags |= FILTRULE_INCLUDE; + /* FALL THROUGH */ + case 'P': + rule->rflags |= FILTRULE_RECEIVER_SIDE; + prefix_specifies_side = True; + break; + case '!': + rule->rflags |= FILTRULE_CLEAR_LIST; + break; + default: + rprintf(FERROR, "Unknown filter rule: `%s'\n", *rulestr_ptr); + exit_cleanup(RERR_SYNTAX); + } + while (ch != '!' && *++s && *s != ' ' && *s != '_') { + if (template->rflags & FILTRULE_WORD_SPLIT && isspace(*s)) { + s--; + break; + } + switch (*s) { + default: + invalid: + rprintf(FERROR, + "invalid modifier '%c' at position %d in filter rule: %s\n", + *s, (int)(s - (const uchar *)*rulestr_ptr), *rulestr_ptr); + exit_cleanup(RERR_SYNTAX); + case '-': + if (!BITS_SETnUNSET(rule->rflags, FILTRULE_MERGE_FILE, FILTRULE_NO_PREFIXES)) + goto invalid; + rule->rflags |= FILTRULE_NO_PREFIXES; + break; + case '+': + if (!BITS_SETnUNSET(rule->rflags, FILTRULE_MERGE_FILE, FILTRULE_NO_PREFIXES)) + goto invalid; + rule->rflags |= FILTRULE_NO_PREFIXES + | FILTRULE_INCLUDE; + break; + case '/': + rule->rflags |= FILTRULE_ABS_PATH; + break; + case '!': + /* Negation really goes with the pattern, so it + * isn't useful as a merge-file default. */ + if (rule->rflags & FILTRULE_MERGE_FILE) + goto invalid; + rule->rflags |= FILTRULE_NEGATE; + break; + case 'C': + if (rule->rflags & FILTRULE_NO_PREFIXES || prefix_specifies_side) + goto invalid; + rule->rflags |= FILTRULE_NO_PREFIXES + | FILTRULE_WORD_SPLIT + | FILTRULE_NO_INHERIT + | FILTRULE_CVS_IGNORE; + break; + case 'e': + if (!(rule->rflags & FILTRULE_MERGE_FILE)) + goto invalid; + rule->rflags |= FILTRULE_EXCLUDE_SELF; + break; + case 'n': + if (!(rule->rflags & FILTRULE_MERGE_FILE)) + goto invalid; + rule->rflags |= FILTRULE_NO_INHERIT; + break; + case 'p': + rule->rflags |= FILTRULE_PERISHABLE; + break; + case 'r': + if (prefix_specifies_side) + goto invalid; + rule->rflags |= FILTRULE_RECEIVER_SIDE; + break; + case 's': + if (prefix_specifies_side) + goto invalid; + rule->rflags |= FILTRULE_SENDER_SIDE; + break; + case 'w': + if (!(rule->rflags & FILTRULE_MERGE_FILE)) + goto invalid; + rule->rflags |= FILTRULE_WORD_SPLIT; + break; + } + } + if (*s) + s++; + } + if (template->rflags & FILTRULES_SIDES) { + if (rule->rflags & FILTRULES_SIDES) { + /* The filter and template both specify side(s). This + * is dodgy (and won't work correctly if the template is + * a one-sided per-dir merge rule), so reject it. */ + rprintf(FERROR, + "specified-side merge file contains specified-side filter: %s\n", + *rulestr_ptr); + exit_cleanup(RERR_SYNTAX); + } + rule->rflags |= template->rflags & FILTRULES_SIDES; + } + + if (template->rflags & FILTRULE_WORD_SPLIT) { + const uchar *cp = s; + /* Token ends at whitespace or the end of the string. */ + while (!isspace(*cp) && *cp != '\0') + cp++; + len = cp - s; + } else + len = strlen((char*)s); + + if (rule->rflags & FILTRULE_CLEAR_LIST) { + if (!(rule->rflags & FILTRULE_NO_PREFIXES) + && !(xflags & XFLG_OLD_PREFIXES) && len) { + rprintf(FERROR, + "'!' rule has trailing characters: %s\n", *rulestr_ptr); + exit_cleanup(RERR_SYNTAX); + } + if (len > 1) + rule->rflags &= ~FILTRULE_CLEAR_LIST; + } else if (!len && !(rule->rflags & FILTRULE_CVS_IGNORE)) { + rprintf(FERROR, "unexpected end of filter rule: %s\n", *rulestr_ptr); + exit_cleanup(RERR_SYNTAX); + } + + /* --delete-excluded turns an un-modified include/exclude into a sender-side rule. */ + if (delete_excluded + && !(rule->rflags & (FILTRULES_SIDES|FILTRULE_MERGE_FILE|FILTRULE_PERDIR_MERGE))) + rule->rflags |= FILTRULE_SENDER_SIDE; + + *pat_ptr = (const char *)s; + *pat_len_ptr = len; + *rulestr_ptr = *pat_ptr + len; + return rule; +} + +static char default_cvsignore[] = + /* These default ignored items come from the CVS manual. */ + "RCS SCCS CVS CVS.adm RCSLOG cvslog.* tags TAGS" + " .make.state .nse_depinfo *~ #* .#* ,* _$* *$" + " *.old *.bak *.BAK *.orig *.rej .del-*" + " *.a *.olb *.o *.obj *.so *.exe" + " *.Z *.elc *.ln core" + /* The rest we added to suit ourself. */ + " .svn/ .git/ .hg/ .bzr/"; + +static void get_cvs_excludes(uint32 rflags) +{ + static int initialized = 0; + char *p, fname[MAXPATHLEN]; + + if (initialized) + return; + initialized = 1; + + parse_filter_str(&cvs_filter_list, default_cvsignore, + rule_template(rflags | (protocol_version >= 30 ? FILTRULE_PERISHABLE : 0)), + 0); + + p = module_id >= 0 && lp_use_chroot(module_id) ? "/" : getenv("HOME"); + if (p && pathjoin(fname, MAXPATHLEN, p, ".cvsignore") < MAXPATHLEN) + parse_filter_file(&cvs_filter_list, fname, rule_template(rflags), 0); + + parse_filter_str(&cvs_filter_list, getenv("CVSIGNORE"), rule_template(rflags), 0); +} + +const filter_rule *rule_template(uint32 rflags) +{ + static filter_rule template; /* zero-initialized */ + template.rflags = rflags; + return &template; +} + +void parse_filter_str(filter_rule_list *listp, const char *rulestr, + const filter_rule *template, int xflags) +{ + filter_rule *rule; + const char *pat; + unsigned int pat_len; + + if (!rulestr) + return; + + while (1) { + uint32 new_rflags; + + /* Remember that the returned string is NOT '\0' terminated! */ + if (!(rule = parse_rule_tok(&rulestr, template, xflags, &pat, &pat_len))) + break; + + if (pat_len >= MAXPATHLEN) { + rprintf(FERROR, "discarding over-long filter: %.*s\n", + (int)pat_len, pat); + free_continue: + free_filter(rule); + continue; + } + + new_rflags = rule->rflags; + if (new_rflags & FILTRULE_CLEAR_LIST) { + if (DEBUG_GTE(FILTER, 2)) { + rprintf(FINFO, + "[%s] clearing filter list%s\n", + who_am_i(), listp->debug_type); + } + clear_filter_list(listp); + goto free_continue; + } + + if (new_rflags & FILTRULE_MERGE_FILE) { + if (!pat_len) { + pat = ".cvsignore"; + pat_len = 10; + } + if (new_rflags & FILTRULE_EXCLUDE_SELF) { + const char *name; + filter_rule *excl_self; + + if (!(excl_self = new0(filter_rule))) + out_of_memory("parse_filter_str"); + /* Find the beginning of the basename and add an exclude for it. */ + for (name = pat + pat_len; name > pat && name[-1] != '/'; name--) {} + add_rule(listp, name, (pat + pat_len) - name, excl_self, 0); + rule->rflags &= ~FILTRULE_EXCLUDE_SELF; + } + if (new_rflags & FILTRULE_PERDIR_MERGE) { + if (parent_dirscan) { + const char *p; + unsigned int len = pat_len; + if ((p = parse_merge_name(pat, &len, module_dirlen))) + add_rule(listp, p, len, rule, 0); + else + free_filter(rule); + continue; + } + } else { + const char *p; + unsigned int len = pat_len; + if ((p = parse_merge_name(pat, &len, 0))) + parse_filter_file(listp, p, rule, XFLG_FATAL_ERRORS); + free_filter(rule); + continue; + } + } + + add_rule(listp, pat, pat_len, rule, xflags); + + if (new_rflags & FILTRULE_CVS_IGNORE + && !(new_rflags & FILTRULE_MERGE_FILE)) + get_cvs_excludes(new_rflags); + } +} + +void parse_filter_file(filter_rule_list *listp, const char *fname, const filter_rule *template, int xflags) +{ + FILE *fp; + char line[BIGPATHBUFLEN]; + char *eob = line + sizeof line - 1; + BOOL word_split = (template->rflags & FILTRULE_WORD_SPLIT) != 0; + + if (!fname || !*fname) + return; + + if (*fname != '-' || fname[1] || am_server) { + if (daemon_filter_list.head) { + strlcpy(line, fname, sizeof line); + clean_fname(line, CFN_COLLAPSE_DOT_DOT_DIRS); + if (check_filter(&daemon_filter_list, FLOG, line, 0) < 0) + fp = NULL; + else + fp = fopen(line, "rb"); + } else + fp = fopen(fname, "rb"); + } else + fp = stdin; + + if (DEBUG_GTE(FILTER, 2)) { + rprintf(FINFO, "[%s] parse_filter_file(%s,%x,%x)%s\n", + who_am_i(), fname, template->rflags, xflags, + fp ? "" : " [not found]"); + } + + if (!fp) { + if (xflags & XFLG_FATAL_ERRORS) { + rsyserr(FERROR, errno, + "failed to open %sclude file %s", + template->rflags & FILTRULE_INCLUDE ? "in" : "ex", + fname); + exit_cleanup(RERR_FILEIO); + } + return; + } + dirbuf[dirbuf_len] = '\0'; + + while (1) { + char *s = line; + int ch, overflow = 0; + while (1) { + if ((ch = getc(fp)) == EOF) { + if (ferror(fp) && errno == EINTR) { + clearerr(fp); + continue; + } + break; + } + if (word_split && isspace(ch)) + break; + if (eol_nulls? !ch : (ch == '\n' || ch == '\r')) + break; + if (s < eob) + *s++ = ch; + else + overflow = 1; + } + if (overflow) { + rprintf(FERROR, "discarding over-long filter: %s...\n", line); + s = line; + } + *s = '\0'; + /* Skip an empty token and (when line parsing) comments. */ + if (*line && (word_split || (*line != ';' && *line != '#'))) + parse_filter_str(listp, line, template, xflags); + if (ch == EOF) + break; + } + fclose(fp); +} + +/* If the "for_xfer" flag is set, the prefix is made compatible with the + * current protocol_version (if possible) or a NULL is returned (if not + * possible). */ +char *get_rule_prefix(filter_rule *rule, const char *pat, int for_xfer, + unsigned int *plen_ptr) +{ + static char buf[MAX_RULE_PREFIX+1]; + char *op = buf; + int legal_len = for_xfer && protocol_version < 29 ? 1 : MAX_RULE_PREFIX-1; + + if (rule->rflags & FILTRULE_PERDIR_MERGE) { + if (legal_len == 1) + return NULL; + *op++ = ':'; + } else if (rule->rflags & FILTRULE_INCLUDE) + *op++ = '+'; + else if (legal_len != 1 + || ((*pat == '-' || *pat == '+') && pat[1] == ' ')) + *op++ = '-'; + else + legal_len = 0; + + if (rule->rflags & FILTRULE_ABS_PATH) + *op++ = '/'; + if (rule->rflags & FILTRULE_NEGATE) + *op++ = '!'; + if (rule->rflags & FILTRULE_CVS_IGNORE) + *op++ = 'C'; + else { + if (rule->rflags & FILTRULE_NO_INHERIT) + *op++ = 'n'; + if (rule->rflags & FILTRULE_WORD_SPLIT) + *op++ = 'w'; + if (rule->rflags & FILTRULE_NO_PREFIXES) { + if (rule->rflags & FILTRULE_INCLUDE) + *op++ = '+'; + else + *op++ = '-'; + } + } + if (rule->rflags & FILTRULE_EXCLUDE_SELF) + *op++ = 'e'; + if (rule->rflags & FILTRULE_SENDER_SIDE + && (!for_xfer || protocol_version >= 29)) + *op++ = 's'; + if (rule->rflags & FILTRULE_RECEIVER_SIDE + && (!for_xfer || protocol_version >= 29 + || (delete_excluded && am_sender))) + *op++ = 'r'; + if (rule->rflags & FILTRULE_PERISHABLE) { + if (!for_xfer || protocol_version >= 30) + *op++ = 'p'; + else if (am_sender) + return NULL; + } + if (op - buf > legal_len) + return NULL; + if (legal_len) + *op++ = ' '; + *op = '\0'; + if (plen_ptr) + *plen_ptr = op - buf; + return buf; +} + +static void send_rules(int f_out, filter_rule_list *flp) +{ + filter_rule *ent, *prev = NULL; + + for (ent = flp->head; ent; ent = ent->next) { + unsigned int len, plen, dlen; + int elide = 0; + char *p; + + /* Note we need to check delete_excluded here in addition to + * the code in parse_rule_tok() because some rules may have + * been added before we found the --delete-excluded option. + * We must also elide any CVS merge-file rules to avoid a + * backward compatibility problem, and we elide any no-prefix + * merge files as an optimization (since they can only have + * include/exclude rules). */ + if (ent->rflags & FILTRULE_SENDER_SIDE) + elide = am_sender ? 1 : -1; + if (ent->rflags & FILTRULE_RECEIVER_SIDE) + elide = elide ? 0 : am_sender ? -1 : 1; + else if (delete_excluded && !elide + && (!(ent->rflags & FILTRULE_PERDIR_MERGE) + || ent->rflags & FILTRULE_NO_PREFIXES)) + elide = am_sender ? 1 : -1; + if (elide < 0) { + if (prev) + prev->next = ent->next; + else + flp->head = ent->next; + } else + prev = ent; + if (elide > 0) + continue; + if (ent->rflags & FILTRULE_CVS_IGNORE + && !(ent->rflags & FILTRULE_MERGE_FILE)) { + int f = am_sender || protocol_version < 29 ? f_out : -2; + send_rules(f, &cvs_filter_list); + if (f == f_out) + continue; + } + p = get_rule_prefix(ent, ent->pattern, 1, &plen); + if (!p) { + rprintf(FERROR, + "filter rules are too modern for remote rsync.\n"); + exit_cleanup(RERR_PROTOCOL); + } + if (f_out < 0) + continue; + len = strlen(ent->pattern); + dlen = ent->rflags & FILTRULE_DIRECTORY ? 1 : 0; + if (!(plen + len + dlen)) + continue; + write_int(f_out, plen + len + dlen); + if (plen) + write_buf(f_out, p, plen); + write_buf(f_out, ent->pattern, len); + if (dlen) + write_byte(f_out, '/'); + } + flp->tail = prev; +} + +/* This is only called by the client. */ +void send_filter_list(int f_out) +{ + int receiver_wants_list = prune_empty_dirs + || (delete_mode && (!delete_excluded || protocol_version >= 29)); + + if (local_server || (am_sender && !receiver_wants_list)) + f_out = -1; + if (cvs_exclude && am_sender) { + if (protocol_version >= 29) + parse_filter_str(&filter_list, ":C", rule_template(0), 0); + parse_filter_str(&filter_list, "-C", rule_template(0), 0); + } + + send_rules(f_out, &filter_list); + + if (f_out >= 0) + write_int(f_out, 0); + + if (cvs_exclude) { + if (!am_sender || protocol_version < 29) + parse_filter_str(&filter_list, ":C", rule_template(0), 0); + if (!am_sender) + parse_filter_str(&filter_list, "-C", rule_template(0), 0); + } +} + +/* This is only called by the server. */ +void recv_filter_list(int f_in) +{ + char line[BIGPATHBUFLEN]; + int xflags = protocol_version >= 29 ? 0 : XFLG_OLD_PREFIXES; + int receiver_wants_list = prune_empty_dirs + || (delete_mode + && (!delete_excluded || protocol_version >= 29)); + unsigned int len; + + if (!local_server && (am_sender || receiver_wants_list)) { + while ((len = read_int(f_in)) != 0) { + if (len >= sizeof line) + overflow_exit("recv_rules"); + read_sbuf(f_in, line, len); + parse_filter_str(&filter_list, line, rule_template(0), xflags); + } + } + + if (cvs_exclude) { + if (local_server || am_sender || protocol_version < 29) + parse_filter_str(&filter_list, ":C", rule_template(0), 0); + if (local_server || am_sender) + parse_filter_str(&filter_list, "-C", rule_template(0), 0); + } + + if (local_server) /* filter out any rules that aren't for us. */ + send_rules(-1, &filter_list); +} diff --git a/rsync/fileio.c b/rsync/fileio.c new file mode 100644 index 0000000..abef46d --- /dev/null +++ b/rsync/fileio.c @@ -0,0 +1,288 @@ +/* + * File IO utilities used in rsync. + * + * Copyright (C) 1998 Andrew Tridgell + * Copyright (C) 2002 Martin Pool + * Copyright (C) 2004-2014 Wayne Davison + * + * 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, visit the http://fsf.org website. + */ + +#include "rsync.h" +#include "inums.h" + +#ifndef ENODATA +#define ENODATA EAGAIN +#endif + +/* We want all reads to be aligned on 1K boundries. */ +#define ALIGN_BOUNDRY 1024 +/* How far past the boundary is an offset? */ +#define ALIGNED_OVERSHOOT(oft) ((oft) & (ALIGN_BOUNDRY-1)) +/* Round up a length to the next boundary */ +#define ALIGNED_LENGTH(len) ((((len) - 1) | (ALIGN_BOUNDRY-1)) + 1) + +extern int sparse_files; + +static OFF_T sparse_seek = 0; + +int sparse_end(int f, OFF_T size) +{ + int ret; + + if (!sparse_seek) + return 0; + +#ifdef HAVE_FTRUNCATE + ret = do_ftruncate(f, size); +#else + if (do_lseek(f, sparse_seek-1, SEEK_CUR) != size-1) + ret = -1; + else { + do { + ret = write(f, "", 1); + } while (ret < 0 && errno == EINTR); + + ret = ret <= 0 ? -1 : 0; + } +#endif + + sparse_seek = 0; + + return ret; +} + + +static int write_sparse(int f, char *buf, int len) +{ + int l1 = 0, l2 = 0; + int ret; + + for (l1 = 0; l1 < len && buf[l1] == 0; l1++) {} + for (l2 = 0; l2 < len-l1 && buf[len-(l2+1)] == 0; l2++) {} + + sparse_seek += l1; + + if (l1 == len) + return len; + + if (sparse_seek) + do_lseek(f, sparse_seek, SEEK_CUR); + sparse_seek = l2; + + while ((ret = write(f, buf + l1, len - (l1+l2))) <= 0) { + if (ret < 0 && errno == EINTR) + continue; + sparse_seek = 0; + return ret; + } + + if (ret != (int)(len - (l1+l2))) { + sparse_seek = 0; + return l1+ret; + } + + return len; +} + + +static char *wf_writeBuf; +static size_t wf_writeBufSize; +static size_t wf_writeBufCnt; + +int flush_write_file(int f) +{ + int ret = 0; + char *bp = wf_writeBuf; + + while (wf_writeBufCnt > 0) { + if ((ret = write(f, bp, wf_writeBufCnt)) < 0) { + if (errno == EINTR) + continue; + return ret; + } + wf_writeBufCnt -= ret; + bp += ret; + } + return ret; +} + + +/* + * write_file does not allow incomplete writes. It loops internally + * until len bytes are written or errno is set. + */ +int write_file(int f, char *buf, int len) +{ + int ret = 0; + + while (len > 0) { + int r1; + if (sparse_files > 0) { + int len1 = MIN(len, SPARSE_WRITE_SIZE); + r1 = write_sparse(f, buf, len1); + } else { + if (!wf_writeBuf) { + wf_writeBufSize = WRITE_SIZE * 8; + wf_writeBufCnt = 0; + wf_writeBuf = new_array(char, wf_writeBufSize); + if (!wf_writeBuf) + out_of_memory("write_file"); + } + r1 = (int)MIN((size_t)len, wf_writeBufSize - wf_writeBufCnt); + if (r1) { + memcpy(wf_writeBuf + wf_writeBufCnt, buf, r1); + wf_writeBufCnt += r1; + } + if (wf_writeBufCnt == wf_writeBufSize) { + if (flush_write_file(f) < 0) + return -1; + if (!r1 && len) + continue; + } + } + if (r1 <= 0) { + if (ret > 0) + return ret; + return r1; + } + len -= r1; + buf += r1; + ret += r1; + } + return ret; +} + + +/* This provides functionality somewhat similar to mmap() but using read(). + * It gives sliding window access to a file. mmap() is not used because of + * the possibility of another program (such as a mailer) truncating the + * file thus giving us a SIGBUS. */ +struct map_struct *map_file(int fd, OFF_T len, int32 read_size, int32 blk_size) +{ + struct map_struct *map; + + if (!(map = new0(struct map_struct))) + out_of_memory("map_file"); + + if (blk_size && (read_size % blk_size)) + read_size += blk_size - (read_size % blk_size); + + map->fd = fd; + map->file_size = len; + map->def_window_size = ALIGNED_LENGTH(read_size); + + return map; +} + + +/* slide the read window in the file */ +char *map_ptr(struct map_struct *map, OFF_T offset, int32 len) +{ + OFF_T window_start, read_start; + int32 window_size, read_size, read_offset, align_fudge; + + if (len == 0) + return NULL; + if (len < 0) { + rprintf(FERROR, "invalid len passed to map_ptr: %ld\n", + (long)len); + exit_cleanup(RERR_FILEIO); + } + + /* in most cases the region will already be available */ + if (offset >= map->p_offset && offset+len <= map->p_offset+map->p_len) + return map->p + (offset - map->p_offset); + + /* nope, we are going to have to do a read. Work out our desired window */ + align_fudge = (int32)ALIGNED_OVERSHOOT(offset); + window_start = offset - align_fudge; + window_size = map->def_window_size; + if (window_start + window_size > map->file_size) + window_size = (int32)(map->file_size - window_start); + if (window_size < len + align_fudge) + window_size = ALIGNED_LENGTH(len + align_fudge); + + /* make sure we have allocated enough memory for the window */ + if (window_size > map->p_size) { + map->p = realloc_array(map->p, char, window_size); + if (!map->p) + out_of_memory("map_ptr"); + map->p_size = window_size; + } + + /* Now try to avoid re-reading any bytes by reusing any bytes from the previous buffer. */ + if (window_start >= map->p_offset && window_start < map->p_offset + map->p_len + && window_start + window_size >= map->p_offset + map->p_len) { + read_start = map->p_offset + map->p_len; + read_offset = (int32)(read_start - window_start); + read_size = window_size - read_offset; + memmove(map->p, map->p + (map->p_len - read_offset), read_offset); + } else { + read_start = window_start; + read_size = window_size; + read_offset = 0; + } + + if (read_size <= 0) { + rprintf(FERROR, "invalid read_size of %ld in map_ptr\n", + (long)read_size); + exit_cleanup(RERR_FILEIO); + } + + if (map->p_fd_offset != read_start) { + OFF_T ret = do_lseek(map->fd, read_start, SEEK_SET); + if (ret != read_start) { + rsyserr(FERROR, errno, "lseek returned %s, not %s", + big_num(ret), big_num(read_start)); + exit_cleanup(RERR_FILEIO); + } + map->p_fd_offset = read_start; + } + map->p_offset = window_start; + map->p_len = window_size; + + while (read_size > 0) { + int32 nread = read(map->fd, map->p + read_offset, read_size); + if (nread <= 0) { + if (!map->status) + map->status = nread ? errno : ENODATA; + /* The best we can do is zero the buffer -- the file + * has changed mid transfer! */ + memset(map->p + read_offset, 0, read_size); + break; + } + map->p_fd_offset += nread; + read_offset += nread; + read_size -= nread; + } + + return map->p + align_fudge; +} + + +int unmap_file(struct map_struct *map) +{ + int ret; + + if (map->p) { + free(map->p); + map->p = NULL; + } + ret = map->status; + memset(map, 0, sizeof map[0]); + free(map); + + return ret; +} diff --git a/rsync/flist.c b/rsync/flist.c new file mode 100644 index 0000000..c24672e --- /dev/null +++ b/rsync/flist.c @@ -0,0 +1,3231 @@ +/* + * Generate and receive file lists. + * + * Copyright (C) 1996 Andrew Tridgell + * Copyright (C) 1996 Paul Mackerras + * Copyright (C) 2001, 2002 Martin Pool + * Copyright (C) 2002-2014 Wayne Davison + * + * 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, visit the http://fsf.org website. + */ + +#include "rsync.h" +#include "ifuncs.h" +#include "rounding.h" +#include "inums.h" +#include "io.h" + +extern int am_root; +extern int am_server; +extern int am_daemon; +extern int am_sender; +extern int am_generator; +extern int inc_recurse; +extern int always_checksum; +extern int module_id; +extern int ignore_errors; +extern int numeric_ids; +extern int recurse; +extern int use_qsort; +extern int xfer_dirs; +extern int filesfrom_fd; +extern int one_file_system; +extern int copy_dirlinks; +extern int preserve_uid; +extern int preserve_gid; +extern int preserve_acls; +extern int preserve_xattrs; +extern int preserve_links; +extern int preserve_hard_links; +extern int preserve_devices; +extern int preserve_specials; +extern int delete_during; +extern int missing_args; +extern int eol_nulls; +extern int relative_paths; +extern int implied_dirs; +extern int ignore_perishable; +extern int non_perishable_cnt; +extern int prune_empty_dirs; +extern int copy_links; +extern int copy_unsafe_links; +extern int protocol_version; +extern int sanitize_paths; +extern int munge_symlinks; +extern int use_safe_inc_flist; +extern int need_unsorted_flist; +extern int sender_symlink_iconv; +extern int output_needs_newline; +extern int sender_keeps_checksum; +extern int unsort_ndx; +extern uid_t our_uid; +extern struct stats stats; +extern char *filesfrom_host; +extern char *usermap, *groupmap; + +extern char curr_dir[MAXPATHLEN]; + +extern struct chmod_mode_struct *chmod_modes; + +extern filter_rule_list filter_list; +extern filter_rule_list daemon_filter_list; + +#ifdef ICONV_OPTION +extern int filesfrom_convert; +extern iconv_t ic_send, ic_recv; +#endif + +#define PTR_SIZE (sizeof (struct file_struct *)) + +int io_error; +int checksum_len; +dev_t filesystem_dev; /* used to implement -x */ + +struct file_list *cur_flist, *first_flist, *dir_flist; +int send_dir_ndx = -1, send_dir_depth = -1; +int flist_cnt = 0; /* how many (non-tmp) file list objects exist */ +int file_total = 0; /* total of all active items over all file-lists */ +int file_old_total = 0; /* total of active items that will soon be gone */ +int flist_eof = 0; /* all the file-lists are now known */ + +#define NORMAL_NAME 0 +#define SLASH_ENDING_NAME 1 +#define DOTDIR_NAME 2 +#define MISSING_NAME 3 + +/* Starting from protocol version 26, we always use 64-bit ino_t and dev_t + * internally, even if this platform does not allow files to have 64-bit inums. + * The only exception is if we're on a platform with no 64-bit type at all. + * + * Because we use read_longint() to get these off the wire, if you transfer + * devices or (for protocols < 30) hardlinks with dev or inum > 2**32 to a + * machine with no 64-bit types then you will get an overflow error. + * + * Note that if you transfer devices from a 64-bit-devt machine (say, Solaris) + * to a 32-bit-devt machine (say, Linux-2.2/x86) then the device numbers will + * be truncated. But it's a kind of silly thing to do anyhow. */ + +/* The tmp_* vars are used as a cache area by make_file() to store data + * that the sender doesn't need to remember in its file list. The data + * will survive just long enough to be used by send_file_entry(). */ +static dev_t tmp_rdev; +#ifdef SUPPORT_HARD_LINKS +static int64 tmp_dev = -1, tmp_ino; +#endif +static char tmp_sum[MAX_DIGEST_LEN]; + +static char empty_sum[MAX_DIGEST_LEN]; +static int flist_count_offset; /* for --delete --progress */ + +static void flist_sort_and_clean(struct file_list *flist, int strip_root); +static void output_flist(struct file_list *flist); + +void init_flist(void) +{ + if (DEBUG_GTE(FLIST, 4)) { + rprintf(FINFO, "FILE_STRUCT_LEN=%d, EXTRA_LEN=%d\n", + (int)FILE_STRUCT_LEN, (int)EXTRA_LEN); + } + checksum_len = protocol_version < 21 ? 2 + : protocol_version < 30 ? MD4_DIGEST_LEN + : MD5_DIGEST_LEN; +} + +static int show_filelist_p(void) +{ + return INFO_GTE(FLIST, 1) && xfer_dirs && !am_server && !inc_recurse; +} + +static void start_filelist_progress(char *kind) +{ + rprintf(FCLIENT, "%s ... ", kind); + output_needs_newline = 1; + rflush(FINFO); +} + +static void emit_filelist_progress(int count) +{ + rprintf(FCLIENT, " %d files...\r", count); +} + +static void maybe_emit_filelist_progress(int count) +{ + if (INFO_GTE(FLIST, 2) && show_filelist_p() && (count % 100) == 0) + emit_filelist_progress(count); +} + +static void finish_filelist_progress(const struct file_list *flist) +{ + if (INFO_GTE(FLIST, 2)) { + /* This overwrites the progress line */ + rprintf(FINFO, "%d file%sto consider\n", + flist->used, flist->used == 1 ? " " : "s "); + } else { + output_needs_newline = 0; + rprintf(FINFO, "done\n"); + } +} + +void show_flist_stats(void) +{ + /* Nothing yet */ +} + +/* Stat either a symlink or its referent, depending on the settings of + * copy_links, copy_unsafe_links, etc. Returns -1 on error, 0 on success. + * + * If path is the name of a symlink, then the linkbuf buffer (which must hold + * MAXPATHLEN chars) will be set to the symlink's target string. + * + * The stat structure pointed to by stp will contain information about the + * link or the referent as appropriate, if they exist. */ +static int readlink_stat(const char *path, STRUCT_STAT *stp, char *linkbuf) +{ +#ifdef SUPPORT_LINKS + if (link_stat(path, stp, copy_dirlinks) < 0) + return -1; + if (S_ISLNK(stp->st_mode)) { + int llen = do_readlink(path, linkbuf, MAXPATHLEN - 1); + if (llen < 0) + return -1; + linkbuf[llen] = '\0'; + if (copy_unsafe_links && unsafe_symlink(linkbuf, path)) { + if (INFO_GTE(SYMSAFE, 1)) { + rprintf(FINFO,"copying unsafe symlink \"%s\" -> \"%s\"\n", + path, linkbuf); + } + return x_stat(path, stp, NULL); + } + if (munge_symlinks && am_sender && llen > SYMLINK_PREFIX_LEN + && strncmp(linkbuf, SYMLINK_PREFIX, SYMLINK_PREFIX_LEN) == 0) { + memmove(linkbuf, linkbuf + SYMLINK_PREFIX_LEN, + llen - SYMLINK_PREFIX_LEN + 1); + } + } + return 0; +#else + return x_stat(path, stp, NULL); +#endif +} + +int link_stat(const char *path, STRUCT_STAT *stp, int follow_dirlinks) +{ +#ifdef SUPPORT_LINKS + if (copy_links) + return x_stat(path, stp, NULL); + if (x_lstat(path, stp, NULL) < 0) + return -1; + if (follow_dirlinks && S_ISLNK(stp->st_mode)) { + STRUCT_STAT st; + if (x_stat(path, &st, NULL) == 0 && S_ISDIR(st.st_mode)) + *stp = st; + } + return 0; +#else + return x_stat(path, stp, NULL); +#endif +} + +static inline int is_daemon_excluded(const char *fname, int is_dir) +{ + if (daemon_filter_list.head + && check_filter(&daemon_filter_list, FLOG, fname, is_dir) < 0) { + errno = ENOENT; + return 1; + } + return 0; +} + +static inline int path_is_daemon_excluded(char *path, int ignore_filename) +{ + if (daemon_filter_list.head) { + char *slash = path; + + while ((slash = strchr(slash+1, '/')) != NULL) { + int ret; + *slash = '\0'; + ret = check_filter(&daemon_filter_list, FLOG, path, 1); + *slash = '/'; + if (ret < 0) { + errno = ENOENT; + return 1; + } + } + + if (!ignore_filename + && check_filter(&daemon_filter_list, FLOG, path, 1) < 0) { + errno = ENOENT; + return 1; + } + } + + return 0; +} + +/* This function is used to check if a file should be included/excluded + * from the list of files based on its name and type etc. The value of + * filter_level is set to either SERVER_FILTERS or ALL_FILTERS. */ +static int is_excluded(const char *fname, int is_dir, int filter_level) +{ +#if 0 /* This currently never happens, so avoid a useless compare. */ + if (filter_level == NO_FILTERS) + return 0; +#endif + if (is_daemon_excluded(fname, is_dir)) + return 1; + if (filter_level != ALL_FILTERS) + return 0; + if (filter_list.head + && check_filter(&filter_list, FINFO, fname, is_dir) < 0) + return 1; + return 0; +} + +static void send_directory(int f, struct file_list *flist, + char *fbuf, int len, int flags); + +static const char *pathname, *orig_dir; +static int pathname_len; + +/* Make sure flist can hold at least flist->used + extra entries. */ +static void flist_expand(struct file_list *flist, int extra) +{ + struct file_struct **new_ptr; + + if (flist->used + extra <= flist->malloced) + return; + + if (flist->malloced < FLIST_START) + flist->malloced = FLIST_START; + else if (flist->malloced >= FLIST_LINEAR) + flist->malloced += FLIST_LINEAR; + else + flist->malloced *= 2; + + /* In case count jumped or we are starting the list + * with a known size just set it. */ + if (flist->malloced < flist->used + extra) + flist->malloced = flist->used + extra; + + new_ptr = realloc_array(flist->files, struct file_struct *, + flist->malloced); + + if (DEBUG_GTE(FLIST, 1) && flist->malloced != FLIST_START) { + rprintf(FCLIENT, "[%s] expand file_list pointer array to %s bytes, did%s move\n", + who_am_i(), + big_num(sizeof flist->files[0] * flist->malloced), + (new_ptr == flist->files) ? " not" : ""); + } + + flist->files = new_ptr; + + if (!flist->files) + out_of_memory("flist_expand"); +} + +static void flist_done_allocating(struct file_list *flist) +{ + void *ptr = pool_boundary(flist->file_pool, 8*1024); + if (flist->pool_boundary == ptr) + flist->pool_boundary = NULL; /* list didn't use any pool memory */ + else + flist->pool_boundary = ptr; +} + +/* Call this with EITHER (1) "file, NULL, 0" to chdir() to the file's + * F_PATHNAME(), or (2) "NULL, dir, dirlen" to chdir() to the supplied dir, + * with dir == NULL taken to be the starting directory, and dirlen < 0 + * indicating that strdup(dir) should be called and then the -dirlen length + * value checked to ensure that it is not daemon-excluded. */ +int change_pathname(struct file_struct *file, const char *dir, int dirlen) +{ + if (dirlen < 0) { + char *cpy = strdup(dir); + if (*cpy != '/') + change_dir(orig_dir, CD_SKIP_CHDIR); + if (path_is_daemon_excluded(cpy, 0)) + goto chdir_error; + dir = cpy; + dirlen = -dirlen; + } else { + if (file) { + if (pathname == F_PATHNAME(file)) + return 1; + dir = F_PATHNAME(file); + if (dir) + dirlen = strlen(dir); + } else if (pathname == dir) + return 1; + if (dir && *dir != '/') + change_dir(orig_dir, CD_SKIP_CHDIR); + } + + pathname = dir; + pathname_len = dirlen; + + if (!dir) + dir = orig_dir; + + if (!change_dir(dir, CD_NORMAL)) { + chdir_error: + io_error |= IOERR_GENERAL; + rsyserr(FERROR_XFER, errno, "change_dir %s failed", full_fname(dir)); + if (dir != orig_dir) + change_dir(orig_dir, CD_NORMAL); + pathname = NULL; + pathname_len = 0; + return 0; + } + + return 1; +} + +static void send_file_entry(int f, const char *fname, struct file_struct *file, +#ifdef SUPPORT_LINKS + const char *symlink_name, int symlink_len, +#endif + int ndx, int first_ndx) +{ + static time_t modtime; + static mode_t mode; +#ifdef SUPPORT_HARD_LINKS + static int64 dev; +#endif + static dev_t rdev; + static uint32 rdev_major; + static uid_t uid; + static gid_t gid; + static const char *user_name, *group_name; + static char lastname[MAXPATHLEN]; +#ifdef SUPPORT_HARD_LINKS + int first_hlink_ndx = -1; +#endif + int l1, l2; + int xflags; + + /* Initialize starting value of xflags and adjust counts. */ + if (S_ISREG(file->mode)) + xflags = 0; + else if (S_ISDIR(file->mode)) { + stats.num_dirs++; + if (protocol_version >= 30) { + if (file->flags & FLAG_CONTENT_DIR) + xflags = file->flags & FLAG_TOP_DIR; + else if (file->flags & FLAG_IMPLIED_DIR) + xflags = XMIT_TOP_DIR | XMIT_NO_CONTENT_DIR; + else + xflags = XMIT_NO_CONTENT_DIR; + } else + xflags = file->flags & FLAG_TOP_DIR; /* FLAG_TOP_DIR == XMIT_TOP_DIR */ + } else { + if (S_ISLNK(file->mode)) + stats.num_symlinks++; + else if (IS_DEVICE(file->mode)) + stats.num_devices++; + else if (IS_SPECIAL(file->mode)) + stats.num_specials++; + xflags = 0; + } + + if (file->mode == mode) + xflags |= XMIT_SAME_MODE; + else + mode = file->mode; + + if (preserve_devices && IS_DEVICE(mode)) { + if (protocol_version < 28) { + if (tmp_rdev == rdev) + xflags |= XMIT_SAME_RDEV_pre28; + else + rdev = tmp_rdev; + } else { + rdev = tmp_rdev; + if ((uint32)major(rdev) == rdev_major) + xflags |= XMIT_SAME_RDEV_MAJOR; + else + rdev_major = major(rdev); + if (protocol_version < 30 && (uint32)minor(rdev) <= 0xFFu) + xflags |= XMIT_RDEV_MINOR_8_pre30; + } + } else if (preserve_specials && IS_SPECIAL(mode) && protocol_version < 31) { + /* Special files don't need an rdev number, so just make + * the historical transmission of the value efficient. */ + if (protocol_version < 28) + xflags |= XMIT_SAME_RDEV_pre28; + else { + rdev = MAKEDEV(major(rdev), 0); + xflags |= XMIT_SAME_RDEV_MAJOR; + if (protocol_version < 30) + xflags |= XMIT_RDEV_MINOR_8_pre30; + } + } else if (protocol_version < 28) + rdev = MAKEDEV(0, 0); + if (!preserve_uid || ((uid_t)F_OWNER(file) == uid && *lastname)) + xflags |= XMIT_SAME_UID; + else { + uid = F_OWNER(file); + if (!numeric_ids) { + user_name = add_uid(uid); + if (inc_recurse && user_name) + xflags |= XMIT_USER_NAME_FOLLOWS; + } + } + if (!preserve_gid || ((gid_t)F_GROUP(file) == gid && *lastname)) + xflags |= XMIT_SAME_GID; + else { + gid = F_GROUP(file); + if (!numeric_ids) { + group_name = add_gid(gid); + if (inc_recurse && group_name) + xflags |= XMIT_GROUP_NAME_FOLLOWS; + } + } + if (file->modtime == modtime) + xflags |= XMIT_SAME_TIME; + else + modtime = file->modtime; + if (NSEC_BUMP(file) && protocol_version >= 31) + xflags |= XMIT_MOD_NSEC; + +#ifdef SUPPORT_HARD_LINKS + if (tmp_dev != -1) { + if (protocol_version >= 30) { + struct ht_int64_node *np = idev_find(tmp_dev, tmp_ino); + first_hlink_ndx = (int32)(long)np->data - 1; + if (first_hlink_ndx < 0) { + np->data = (void*)(long)(first_ndx + ndx + 1); + xflags |= XMIT_HLINK_FIRST; + } + if (DEBUG_GTE(HLINK, 1)) { + if (first_hlink_ndx >= 0) { + rprintf(FINFO, "[%s] #%d hard-links #%d (%sabbrev)\n", + who_am_i(), first_ndx + ndx, first_hlink_ndx, + first_hlink_ndx >= first_ndx ? "" : "un"); + } else if (DEBUG_GTE(HLINK, 3)) { + rprintf(FINFO, "[%s] dev:inode for #%d is %s:%s\n", + who_am_i(), first_ndx + ndx, + big_num(tmp_dev), big_num(tmp_ino)); + } + } + } else { + if (tmp_dev == dev) { + if (protocol_version >= 28) + xflags |= XMIT_SAME_DEV_pre30; + } else + dev = tmp_dev; + } + xflags |= XMIT_HLINKED; + } +#endif + + for (l1 = 0; + lastname[l1] && (fname[l1] == lastname[l1]) && (l1 < 255); + l1++) {} + l2 = strlen(fname+l1); + + if (l1 > 0) + xflags |= XMIT_SAME_NAME; + if (l2 > 255) + xflags |= XMIT_LONG_NAME; + + /* We must make sure we don't send a zero flag byte or the + * other end will terminate the flist transfer. Note that + * the use of XMIT_TOP_DIR on a non-dir has no meaning, so + * it's harmless way to add a bit to the first flag byte. */ + if (protocol_version >= 28) { + if (!xflags && !S_ISDIR(mode)) + xflags |= XMIT_TOP_DIR; + if ((xflags & 0xFF00) || !xflags) { + xflags |= XMIT_EXTENDED_FLAGS; + write_shortint(f, xflags); + } else + write_byte(f, xflags); + } else { + if (!(xflags & 0xFF)) + xflags |= S_ISDIR(mode) ? XMIT_LONG_NAME : XMIT_TOP_DIR; + write_byte(f, xflags); + } + if (xflags & XMIT_SAME_NAME) + write_byte(f, l1); + if (xflags & XMIT_LONG_NAME) + write_varint30(f, l2); + else + write_byte(f, l2); + write_buf(f, fname + l1, l2); + +#ifdef SUPPORT_HARD_LINKS + if (first_hlink_ndx >= 0) { + write_varint(f, first_hlink_ndx); + if (first_hlink_ndx >= first_ndx) + goto the_end; + } +#endif + + write_varlong30(f, F_LENGTH(file), 3); + if (!(xflags & XMIT_SAME_TIME)) { + if (protocol_version >= 30) + write_varlong(f, modtime, 4); + else + write_int(f, modtime); + } + if (xflags & XMIT_MOD_NSEC) + write_varint(f, F_MOD_NSEC(file)); + if (!(xflags & XMIT_SAME_MODE)) + write_int(f, to_wire_mode(mode)); + if (preserve_uid && !(xflags & XMIT_SAME_UID)) { + if (protocol_version < 30) + write_int(f, uid); + else { + write_varint(f, uid); + if (xflags & XMIT_USER_NAME_FOLLOWS) { + int len = strlen(user_name); + write_byte(f, len); + write_buf(f, user_name, len); + } + } + } + if (preserve_gid && !(xflags & XMIT_SAME_GID)) { + if (protocol_version < 30) + write_int(f, gid); + else { + write_varint(f, gid); + if (xflags & XMIT_GROUP_NAME_FOLLOWS) { + int len = strlen(group_name); + write_byte(f, len); + write_buf(f, group_name, len); + } + } + } + if ((preserve_devices && IS_DEVICE(mode)) + || (preserve_specials && IS_SPECIAL(mode) && protocol_version < 31)) { + if (protocol_version < 28) { + if (!(xflags & XMIT_SAME_RDEV_pre28)) + write_int(f, (int)rdev); + } else { + if (!(xflags & XMIT_SAME_RDEV_MAJOR)) + write_varint30(f, major(rdev)); + if (protocol_version >= 30) + write_varint(f, minor(rdev)); + else if (xflags & XMIT_RDEV_MINOR_8_pre30) + write_byte(f, minor(rdev)); + else + write_int(f, minor(rdev)); + } + } + +#ifdef SUPPORT_LINKS + if (symlink_len) { + write_varint30(f, symlink_len); + write_buf(f, symlink_name, symlink_len); + } +#endif + +#ifdef SUPPORT_HARD_LINKS + if (tmp_dev != -1 && protocol_version < 30) { + /* Older protocols expect the dev number to be transmitted + * 1-incremented so that it is never zero. */ + if (protocol_version < 26) { + /* 32-bit dev_t and ino_t */ + write_int(f, (int32)(dev+1)); + write_int(f, (int32)tmp_ino); + } else { + /* 64-bit dev_t and ino_t */ + if (!(xflags & XMIT_SAME_DEV_pre30)) + write_longint(f, dev+1); + write_longint(f, tmp_ino); + } + } +#endif + + if (always_checksum && (S_ISREG(mode) || protocol_version < 28)) { + const char *sum; + if (S_ISREG(mode)) + sum = tmp_sum; + else { + /* Prior to 28, we sent a useless set of nulls. */ + sum = empty_sum; + } + write_buf(f, sum, checksum_len); + } + +#ifdef SUPPORT_HARD_LINKS + the_end: +#endif + strlcpy(lastname, fname, MAXPATHLEN); + + if (S_ISREG(mode) || S_ISLNK(mode)) + stats.total_size += F_LENGTH(file); +} + +static struct file_struct *recv_file_entry(int f, struct file_list *flist, int xflags) +{ + static int64 modtime; + static mode_t mode; +#ifdef SUPPORT_HARD_LINKS + static int64 dev; +#endif + static dev_t rdev; + static uint32 rdev_major; + static uid_t uid; + static gid_t gid; + static uint16 gid_flags; + static char lastname[MAXPATHLEN], *lastdir; + static int lastdir_depth, lastdir_len = -1; + static unsigned int del_hier_name_len = 0; + static int in_del_hier = 0; + char thisname[MAXPATHLEN]; + unsigned int l1 = 0, l2 = 0; + int alloc_len, basename_len, linkname_len; + int extra_len = file_extra_cnt * EXTRA_LEN; + int first_hlink_ndx = -1; + int64 file_length; + uint32 modtime_nsec; + const char *basename; + struct file_struct *file; + alloc_pool_t *pool; + char *bp; + + if (xflags & XMIT_SAME_NAME) + l1 = read_byte(f); + + if (xflags & XMIT_LONG_NAME) + l2 = read_varint30(f); + else + l2 = read_byte(f); + + if (l2 >= MAXPATHLEN - l1) { + rprintf(FERROR, + "overflow: xflags=0x%x l1=%d l2=%d lastname=%s [%s]\n", + xflags, l1, l2, lastname, who_am_i()); + overflow_exit("recv_file_entry"); + } + + strlcpy(thisname, lastname, l1 + 1); + read_sbuf(f, &thisname[l1], l2); + thisname[l1 + l2] = 0; + + /* Abuse basename_len for a moment... */ + basename_len = strlcpy(lastname, thisname, MAXPATHLEN); + +#ifdef ICONV_OPTION + if (ic_recv != (iconv_t)-1) { + xbuf outbuf, inbuf; + + INIT_CONST_XBUF(outbuf, thisname); + INIT_XBUF(inbuf, lastname, basename_len, (size_t)-1); + + if (iconvbufs(ic_recv, &inbuf, &outbuf, ICB_INIT) < 0) { + io_error |= IOERR_GENERAL; + rprintf(FERROR_UTF8, + "[%s] cannot convert filename: %s (%s)\n", + who_am_i(), lastname, strerror(errno)); + outbuf.len = 0; + } + thisname[outbuf.len] = '\0'; + } +#endif + + if (*thisname + && (clean_fname(thisname, CFN_REFUSE_DOT_DOT_DIRS) < 0 || (!relative_paths && *thisname == '/'))) { + rprintf(FERROR, "ABORTING due to unsafe pathname from sender: %s\n", thisname); + exit_cleanup(RERR_PROTOCOL); + } + + if (sanitize_paths) + sanitize_path(thisname, thisname, "", 0, SP_DEFAULT); + + if ((basename = strrchr(thisname, '/')) != NULL) { + int len = basename++ - thisname; + if (len != lastdir_len || memcmp(thisname, lastdir, len) != 0) { + lastdir = new_array(char, len + 1); + memcpy(lastdir, thisname, len); + lastdir[len] = '\0'; + lastdir_len = len; + lastdir_depth = count_dir_elements(lastdir); + } + } else + basename = thisname; + basename_len = strlen(basename) + 1; /* count the '\0' */ + +#ifdef SUPPORT_HARD_LINKS + if (protocol_version >= 30 + && BITS_SETnUNSET(xflags, XMIT_HLINKED, XMIT_HLINK_FIRST)) { + first_hlink_ndx = read_varint(f); + if (first_hlink_ndx < 0 || first_hlink_ndx >= flist->ndx_start + flist->used) { + rprintf(FERROR, + "hard-link reference out of range: %d (%d)\n", + first_hlink_ndx, flist->ndx_start + flist->used); + exit_cleanup(RERR_PROTOCOL); + } + if (DEBUG_GTE(HLINK, 1)) { + rprintf(FINFO, "[%s] #%d hard-links #%d (%sabbrev)\n", + who_am_i(), flist->used+flist->ndx_start, first_hlink_ndx, + first_hlink_ndx >= flist->ndx_start ? "" : "un"); + } + if (first_hlink_ndx >= flist->ndx_start) { + struct file_struct *first = flist->files[first_hlink_ndx - flist->ndx_start]; + file_length = F_LENGTH(first); + modtime = first->modtime; + modtime_nsec = F_MOD_NSEC(first); + mode = first->mode; + if (preserve_uid) + uid = F_OWNER(first); + if (preserve_gid) + gid = F_GROUP(first); + if (preserve_devices && IS_DEVICE(mode)) { + uint32 *devp = F_RDEV_P(first); + rdev = MAKEDEV(DEV_MAJOR(devp), DEV_MINOR(devp)); + extra_len += DEV_EXTRA_CNT * EXTRA_LEN; + } + if (preserve_links && S_ISLNK(mode)) + linkname_len = strlen(F_SYMLINK(first)) + 1; + else + linkname_len = 0; + goto create_object; + } + } +#endif + + file_length = read_varlong30(f, 3); + if (!(xflags & XMIT_SAME_TIME)) { + if (protocol_version >= 30) { + modtime = read_varlong(f, 4); +#if SIZEOF_TIME_T < SIZEOF_INT64 + if (!am_generator && (int64)(time_t)modtime != modtime) { + rprintf(FERROR_XFER, + "Time value of %s truncated on receiver.\n", + lastname); + } +#endif + } else + modtime = read_int(f); + } + if (xflags & XMIT_MOD_NSEC) + modtime_nsec = read_varint(f); + else + modtime_nsec = 0; + if (!(xflags & XMIT_SAME_MODE)) + mode = from_wire_mode(read_int(f)); + + if (chmod_modes && !S_ISLNK(mode) && mode) + mode = tweak_mode(mode, chmod_modes); + + if (preserve_uid && !(xflags & XMIT_SAME_UID)) { + if (protocol_version < 30) + uid = (uid_t)read_int(f); + else { + uid = (uid_t)read_varint(f); + if (xflags & XMIT_USER_NAME_FOLLOWS) + uid = recv_user_name(f, uid); + else if (inc_recurse && am_root && (!numeric_ids || usermap)) + uid = match_uid(uid); + } + } + if (preserve_gid && !(xflags & XMIT_SAME_GID)) { + if (protocol_version < 30) + gid = (gid_t)read_int(f); + else { + gid = (gid_t)read_varint(f); + gid_flags = 0; + if (xflags & XMIT_GROUP_NAME_FOLLOWS) + gid = recv_group_name(f, gid, &gid_flags); + else if (inc_recurse && (!am_root || !numeric_ids || groupmap)) + gid = match_gid(gid, &gid_flags); + } + } + + if ((preserve_devices && IS_DEVICE(mode)) + || (preserve_specials && IS_SPECIAL(mode) && protocol_version < 31)) { + if (protocol_version < 28) { + if (!(xflags & XMIT_SAME_RDEV_pre28)) + rdev = (dev_t)read_int(f); + } else { + uint32 rdev_minor; + if (!(xflags & XMIT_SAME_RDEV_MAJOR)) + rdev_major = read_varint30(f); + if (protocol_version >= 30) + rdev_minor = read_varint(f); + else if (xflags & XMIT_RDEV_MINOR_8_pre30) + rdev_minor = read_byte(f); + else + rdev_minor = read_int(f); + rdev = MAKEDEV(rdev_major, rdev_minor); + } + if (IS_DEVICE(mode)) + extra_len += DEV_EXTRA_CNT * EXTRA_LEN; + file_length = 0; + } else if (protocol_version < 28) + rdev = MAKEDEV(0, 0); + +#ifdef SUPPORT_LINKS + if (preserve_links && S_ISLNK(mode)) { + linkname_len = read_varint30(f) + 1; /* count the '\0' */ + if (linkname_len <= 0 || linkname_len > MAXPATHLEN) { + rprintf(FERROR, "overflow: linkname_len=%d\n", + linkname_len - 1); + overflow_exit("recv_file_entry"); + } +#ifdef ICONV_OPTION + /* We don't know how much extra room we need to convert + * the as-yet-unread symlink data, so let's hope that a + * double-size buffer is plenty. */ + if (sender_symlink_iconv) + linkname_len *= 2; +#endif + if (munge_symlinks) + linkname_len += SYMLINK_PREFIX_LEN; + } + else +#endif + linkname_len = 0; + +#ifdef SUPPORT_HARD_LINKS + create_object: + if (preserve_hard_links) { + if (protocol_version < 28 && S_ISREG(mode)) + xflags |= XMIT_HLINKED; + if (xflags & XMIT_HLINKED) + extra_len += (inc_recurse+1) * EXTRA_LEN; + } +#endif + +#ifdef SUPPORT_ACLS + /* Directories need an extra int32 for the default ACL. */ + if (preserve_acls && S_ISDIR(mode)) + extra_len += EXTRA_LEN; +#endif + + if (always_checksum && S_ISREG(mode)) + extra_len += SUM_EXTRA_CNT * EXTRA_LEN; + +#if SIZEOF_INT64 >= 8 + if (file_length > 0xFFFFFFFFu && S_ISREG(mode)) + extra_len += EXTRA_LEN; +#endif +#ifdef HAVE_UTIMENSAT + if (modtime_nsec) + extra_len += EXTRA_LEN; +#endif + if (file_length < 0) { + rprintf(FERROR, "Offset underflow: file-length is negative\n"); + exit_cleanup(RERR_UNSUPPORTED); + } + + if (inc_recurse && S_ISDIR(mode)) { + if (one_file_system) { + /* Room to save the dir's device for -x */ + extra_len += DEV_EXTRA_CNT * EXTRA_LEN; + } + pool = dir_flist->file_pool; + } else + pool = flist->file_pool; + +#if EXTRA_ROUNDING > 0 + if (extra_len & (EXTRA_ROUNDING * EXTRA_LEN)) + extra_len = (extra_len | (EXTRA_ROUNDING * EXTRA_LEN)) + EXTRA_LEN; +#endif + + alloc_len = FILE_STRUCT_LEN + extra_len + basename_len + + linkname_len; + bp = pool_alloc(pool, alloc_len, "recv_file_entry"); + + memset(bp, 0, extra_len + FILE_STRUCT_LEN); + bp += extra_len; + file = (struct file_struct *)bp; + bp += FILE_STRUCT_LEN; + + memcpy(bp, basename, basename_len); + +#ifdef SUPPORT_HARD_LINKS + if (xflags & XMIT_HLINKED +#ifndef CAN_HARDLINK_SYMLINK + && !S_ISLNK(mode) +#endif +#ifndef CAN_HARDLINK_SPECIAL + && !IS_SPECIAL(mode) && !IS_DEVICE(mode) +#endif + ) + file->flags |= FLAG_HLINKED; +#endif + file->modtime = (time_t)modtime; +#ifdef HAVE_UTIMENSAT + if (modtime_nsec) { + file->flags |= FLAG_MOD_NSEC; + OPT_EXTRA(file, 0)->unum = modtime_nsec; + } +#endif + file->len32 = (uint32)file_length; +#if SIZEOF_INT64 >= 8 + if (file_length > 0xFFFFFFFFu && S_ISREG(mode)) { +#if SIZEOF_CAPITAL_OFF_T < 8 + rprintf(FERROR, "Offset overflow: attempted 64-bit file-length\n"); + exit_cleanup(RERR_UNSUPPORTED); +#else + file->flags |= FLAG_LENGTH64; + OPT_EXTRA(file, NSEC_BUMP(file))->unum = (uint32)(file_length >> 32); +#endif + } +#endif + file->mode = mode; + if (preserve_uid) + F_OWNER(file) = uid; + if (preserve_gid) { + F_GROUP(file) = gid; + file->flags |= gid_flags; + } + if (unsort_ndx) + F_NDX(file) = flist->used + flist->ndx_start; + + if (basename != thisname) { + file->dirname = lastdir; + F_DEPTH(file) = lastdir_depth + 1; + } else + F_DEPTH(file) = 1; + + if (S_ISDIR(mode)) { + if (basename_len == 1+1 && *basename == '.') /* +1 for '\0' */ + F_DEPTH(file)--; + if (protocol_version >= 30) { + if (!(xflags & XMIT_NO_CONTENT_DIR)) { + if (xflags & XMIT_TOP_DIR) + file->flags |= FLAG_TOP_DIR; + file->flags |= FLAG_CONTENT_DIR; + } else if (xflags & XMIT_TOP_DIR) + file->flags |= FLAG_IMPLIED_DIR; + } else if (xflags & XMIT_TOP_DIR) { + in_del_hier = recurse; + del_hier_name_len = F_DEPTH(file) == 0 ? 0 : l1 + l2; + if (relative_paths && del_hier_name_len > 2 + && lastname[del_hier_name_len-1] == '.' + && lastname[del_hier_name_len-2] == '/') + del_hier_name_len -= 2; + file->flags |= FLAG_TOP_DIR | FLAG_CONTENT_DIR; + } else if (in_del_hier) { + if (!relative_paths || !del_hier_name_len + || (l1 >= del_hier_name_len + && lastname[del_hier_name_len] == '/')) + file->flags |= FLAG_CONTENT_DIR; + else + in_del_hier = 0; + } + } + + if (preserve_devices && IS_DEVICE(mode)) { + uint32 *devp = F_RDEV_P(file); + DEV_MAJOR(devp) = major(rdev); + DEV_MINOR(devp) = minor(rdev); + } + +#ifdef SUPPORT_LINKS + if (linkname_len) { + bp += basename_len; + if (first_hlink_ndx >= flist->ndx_start) { + struct file_struct *first = flist->files[first_hlink_ndx - flist->ndx_start]; + memcpy(bp, F_SYMLINK(first), linkname_len); + } else { + if (munge_symlinks) { + strlcpy(bp, SYMLINK_PREFIX, linkname_len); + bp += SYMLINK_PREFIX_LEN; + linkname_len -= SYMLINK_PREFIX_LEN; + } +#ifdef ICONV_OPTION + if (sender_symlink_iconv) { + xbuf outbuf, inbuf; + + alloc_len = linkname_len; + linkname_len /= 2; + + /* Read the symlink data into the end of our double-sized + * buffer and then convert it into the right spot. */ + INIT_XBUF(inbuf, bp + alloc_len - linkname_len, + linkname_len - 1, (size_t)-1); + read_sbuf(f, inbuf.buf, inbuf.len); + INIT_XBUF(outbuf, bp, 0, alloc_len); + + if (iconvbufs(ic_recv, &inbuf, &outbuf, ICB_INIT) < 0) { + io_error |= IOERR_GENERAL; + rprintf(FERROR_XFER, + "[%s] cannot convert symlink data for: %s (%s)\n", + who_am_i(), full_fname(thisname), strerror(errno)); + bp = (char*)file->basename; + *bp++ = '\0'; + outbuf.len = 0; + } + bp[outbuf.len] = '\0'; + } else +#endif + read_sbuf(f, bp, linkname_len - 1); + if (sanitize_paths && !munge_symlinks && *bp) + sanitize_path(bp, bp, "", lastdir_depth, SP_DEFAULT); + } + } +#endif + +#ifdef SUPPORT_HARD_LINKS + if (preserve_hard_links && xflags & XMIT_HLINKED) { + if (protocol_version >= 30) { + if (xflags & XMIT_HLINK_FIRST) { + F_HL_GNUM(file) = flist->ndx_start + flist->used; + } else + F_HL_GNUM(file) = first_hlink_ndx; + } else { + static int32 cnt = 0; + struct ht_int64_node *np; + int64 ino; + int32 ndx; + if (protocol_version < 26) { + dev = read_int(f); + ino = read_int(f); + } else { + if (!(xflags & XMIT_SAME_DEV_pre30)) + dev = read_longint(f); + ino = read_longint(f); + } + np = idev_find(dev, ino); + ndx = (int32)(long)np->data - 1; + if (ndx < 0) { + ndx = cnt++; + np->data = (void*)(long)cnt; + } + F_HL_GNUM(file) = ndx; + } + } +#endif + + if (always_checksum && (S_ISREG(mode) || protocol_version < 28)) { + if (S_ISREG(mode)) + bp = F_SUM(file); + else { + /* Prior to 28, we get a useless set of nulls. */ + bp = tmp_sum; + } + if (first_hlink_ndx >= flist->ndx_start) { + struct file_struct *first = flist->files[first_hlink_ndx - flist->ndx_start]; + memcpy(bp, F_SUM(first), checksum_len); + } else + read_buf(f, bp, checksum_len); + } + +#ifdef SUPPORT_ACLS + if (preserve_acls && !S_ISLNK(mode)) + receive_acl(f, file); +#endif +#ifdef SUPPORT_XATTRS + if (preserve_xattrs) + receive_xattr(f, file); +#endif + + if (S_ISREG(mode) || S_ISLNK(mode)) + stats.total_size += file_length; + + return file; +} + +/* Create a file_struct for a named file by reading its stat() information + * and performing extensive checks against global options. + * + * Returns a pointer to the new file struct, or NULL if there was an error + * or this file should be excluded. + * + * Note: Any error (here or in send_file_name) that results in the omission of + * an existent source file from the file list should set + * "io_error |= IOERR_GENERAL" to avoid deletion of the file from the + * destination if --delete is on. */ +struct file_struct *make_file(const char *fname, struct file_list *flist, + STRUCT_STAT *stp, int flags, int filter_level) +{ + static char *lastdir; + static int lastdir_len = -1; + struct file_struct *file; + char thisname[MAXPATHLEN]; + char linkname[MAXPATHLEN]; + int alloc_len, basename_len, linkname_len; + int extra_len = file_extra_cnt * EXTRA_LEN; + const char *basename; + alloc_pool_t *pool; + STRUCT_STAT st; + char *bp; + + if (strlcpy(thisname, fname, sizeof thisname) >= sizeof thisname) { + io_error |= IOERR_GENERAL; + rprintf(FERROR_XFER, "skipping overly long name: %s\n", fname); + return NULL; + } + clean_fname(thisname, 0); + if (sanitize_paths) + sanitize_path(thisname, thisname, "", 0, SP_DEFAULT); + + if (stp && (S_ISDIR(stp->st_mode) || IS_MISSING_FILE(*stp))) { + /* This is needed to handle a "symlink/." with a --relative + * dir, or a request to delete a specific file. */ + st = *stp; + *linkname = '\0'; /* make IBM code checker happy */ + } else if (readlink_stat(thisname, &st, linkname) != 0) { + int save_errno = errno; + /* See if file is excluded before reporting an error. */ + if (filter_level != NO_FILTERS + && (is_excluded(thisname, 0, filter_level) + || is_excluded(thisname, 1, filter_level))) { + if (ignore_perishable && save_errno != ENOENT) + non_perishable_cnt++; + return NULL; + } + if (save_errno == ENOENT) { +#ifdef SUPPORT_LINKS + /* When our options tell us to follow a symlink that + * points nowhere, tell the user about the symlink + * instead of giving a "vanished" message. We only + * dereference a symlink if one of the --copy*links + * options was specified, so there's no need for the + * extra lstat() if one of these options isn't on. */ + if ((copy_links || copy_unsafe_links || copy_dirlinks) + && x_lstat(thisname, &st, NULL) == 0 + && S_ISLNK(st.st_mode)) { + io_error |= IOERR_GENERAL; + rprintf(FERROR_XFER, "symlink has no referent: %s\n", + full_fname(thisname)); + } else +#endif + { + enum logcode c = am_daemon && protocol_version < 28 + ? FERROR : FWARNING; + io_error |= IOERR_VANISHED; + rprintf(c, "file has vanished: %s\n", + full_fname(thisname)); + } + } else { + io_error |= IOERR_GENERAL; + rsyserr(FERROR_XFER, save_errno, "readlink_stat(%s) failed", + full_fname(thisname)); + } + return NULL; + } else if (IS_MISSING_FILE(st)) { + io_error |= IOERR_GENERAL; + rprintf(FINFO, "skipping file with bogus (zero) st_mode: %s\n", + full_fname(thisname)); + return NULL; + } + + if (filter_level == NO_FILTERS) + goto skip_filters; + + if (S_ISDIR(st.st_mode)) { + if (!xfer_dirs) { + rprintf(FINFO, "skipping directory %s\n", thisname); + return NULL; + } + /* -x only affects dirs because we need to avoid recursing + * into a mount-point directory, not to avoid copying a + * symlinked file if -L (or similar) was specified. */ + if (one_file_system && st.st_dev != filesystem_dev + && BITS_SETnUNSET(flags, FLAG_CONTENT_DIR, FLAG_TOP_DIR)) { + if (one_file_system > 1) { + if (INFO_GTE(MOUNT, 1)) { + rprintf(FINFO, + "[%s] skipping mount-point dir %s\n", + who_am_i(), thisname); + } + return NULL; + } + flags |= FLAG_MOUNT_DIR; + flags &= ~FLAG_CONTENT_DIR; + } + } else + flags &= ~FLAG_CONTENT_DIR; + + if (is_excluded(thisname, S_ISDIR(st.st_mode) != 0, filter_level)) { + if (ignore_perishable) + non_perishable_cnt++; + return NULL; + } + + if (lp_ignore_nonreadable(module_id)) { +#ifdef SUPPORT_LINKS + if (!S_ISLNK(st.st_mode)) +#endif + if (access(thisname, R_OK) != 0) + return NULL; + } + + skip_filters: + + /* Only divert a directory in the main transfer. */ + if (flist) { + if (flist->prev && S_ISDIR(st.st_mode) + && flags & FLAG_DIVERT_DIRS) { + /* Room for parent/sibling/next-child info. */ + extra_len += DIRNODE_EXTRA_CNT * EXTRA_LEN; + if (relative_paths) + extra_len += PTR_EXTRA_CNT * EXTRA_LEN; + pool = dir_flist->file_pool; + } else + pool = flist->file_pool; + } else { +#ifdef SUPPORT_ACLS + /* Directories need an extra int32 for the default ACL. */ + if (preserve_acls && S_ISDIR(st.st_mode)) + extra_len += EXTRA_LEN; +#endif + pool = NULL; + } + + if (DEBUG_GTE(FLIST, 2)) { + rprintf(FINFO, "[%s] make_file(%s,*,%d)\n", + who_am_i(), thisname, filter_level); + } + + if ((basename = strrchr(thisname, '/')) != NULL) { + int len = basename++ - thisname; + if (len != lastdir_len || memcmp(thisname, lastdir, len) != 0) { + lastdir = new_array(char, len + 1); + memcpy(lastdir, thisname, len); + lastdir[len] = '\0'; + lastdir_len = len; + } + } else + basename = thisname; + basename_len = strlen(basename) + 1; /* count the '\0' */ + +#ifdef SUPPORT_LINKS + linkname_len = S_ISLNK(st.st_mode) ? strlen(linkname) + 1 : 0; +#else + linkname_len = 0; +#endif + +#ifdef ST_MTIME_NSEC + if (st.ST_MTIME_NSEC && protocol_version >= 31) + extra_len += EXTRA_LEN; +#endif +#if SIZEOF_CAPITAL_OFF_T >= 8 + if (st.st_size > 0xFFFFFFFFu && S_ISREG(st.st_mode)) + extra_len += EXTRA_LEN; +#endif + + if (always_checksum && am_sender && S_ISREG(st.st_mode)) { + file_checksum(thisname, &st, tmp_sum); + if (sender_keeps_checksum) + extra_len += SUM_EXTRA_CNT * EXTRA_LEN; + } + +#if EXTRA_ROUNDING > 0 + if (extra_len & (EXTRA_ROUNDING * EXTRA_LEN)) + extra_len = (extra_len | (EXTRA_ROUNDING * EXTRA_LEN)) + EXTRA_LEN; +#endif + + alloc_len = FILE_STRUCT_LEN + extra_len + basename_len + + linkname_len; + if (pool) + bp = pool_alloc(pool, alloc_len, "make_file"); + else { + if (!(bp = new_array(char, alloc_len))) + out_of_memory("make_file"); + } + + memset(bp, 0, extra_len + FILE_STRUCT_LEN); + bp += extra_len; + file = (struct file_struct *)bp; + bp += FILE_STRUCT_LEN; + + memcpy(bp, basename, basename_len); + +#ifdef SUPPORT_HARD_LINKS + if (preserve_hard_links && flist && flist->prev) { + if (protocol_version >= 28 + ? (!S_ISDIR(st.st_mode) && st.st_nlink > 1) + : S_ISREG(st.st_mode)) { + tmp_dev = (int64)st.st_dev; + tmp_ino = (int64)st.st_ino; + } else + tmp_dev = -1; + } +#endif + +#ifdef HAVE_STRUCT_STAT_ST_RDEV + if (IS_DEVICE(st.st_mode)) { + tmp_rdev = st.st_rdev; + st.st_size = 0; + } else if (IS_SPECIAL(st.st_mode)) + st.st_size = 0; +#endif + + file->flags = flags; + file->modtime = st.st_mtime; +#ifdef ST_MTIME_NSEC + if (st.ST_MTIME_NSEC && protocol_version >= 31) { + file->flags |= FLAG_MOD_NSEC; + OPT_EXTRA(file, 0)->unum = st.ST_MTIME_NSEC; + } +#endif + file->len32 = (uint32)st.st_size; +#if SIZEOF_CAPITAL_OFF_T >= 8 + if (st.st_size > 0xFFFFFFFFu && S_ISREG(st.st_mode)) { + file->flags |= FLAG_LENGTH64; + OPT_EXTRA(file, NSEC_BUMP(file))->unum = (uint32)(st.st_size >> 32); + } +#endif + file->mode = st.st_mode; + if (preserve_uid) + F_OWNER(file) = st.st_uid; + if (preserve_gid) + F_GROUP(file) = st.st_gid; + if (am_generator && st.st_uid == our_uid) + file->flags |= FLAG_OWNED_BY_US; + + if (basename != thisname) + file->dirname = lastdir; + +#ifdef SUPPORT_LINKS + if (linkname_len) + memcpy(bp + basename_len, linkname, linkname_len); +#endif + + if (am_sender) + F_PATHNAME(file) = pathname; + else if (!pool) + F_DEPTH(file) = extra_len / EXTRA_LEN; + + if (basename_len == 0+1) { + if (!pool) + unmake_file(file); + return NULL; + } + + if (sender_keeps_checksum && S_ISREG(st.st_mode)) + memcpy(F_SUM(file), tmp_sum, checksum_len); + + if (unsort_ndx) + F_NDX(file) = stats.num_dirs; + + return file; +} + +/* Only called for temporary file_struct entries created by make_file(). */ +void unmake_file(struct file_struct *file) +{ + free(REQ_EXTRA(file, F_DEPTH(file))); +} + +static struct file_struct *send_file_name(int f, struct file_list *flist, + const char *fname, STRUCT_STAT *stp, + int flags, int filter_level) +{ + struct file_struct *file; + + file = make_file(fname, flist, stp, flags, filter_level); + if (!file) + return NULL; + + if (chmod_modes && !S_ISLNK(file->mode) && file->mode) + file->mode = tweak_mode(file->mode, chmod_modes); + + if (f >= 0) { + char fbuf[MAXPATHLEN]; +#ifdef SUPPORT_LINKS + const char *symlink_name; + int symlink_len; +#ifdef ICONV_OPTION + char symlink_buf[MAXPATHLEN]; +#endif +#endif +#if defined SUPPORT_ACLS || defined SUPPORT_XATTRS + stat_x sx; + init_stat_x(&sx); +#endif + +#ifdef SUPPORT_LINKS + if (preserve_links && S_ISLNK(file->mode)) { + symlink_name = F_SYMLINK(file); + symlink_len = strlen(symlink_name); + if (symlink_len == 0) { + io_error |= IOERR_GENERAL; + f_name(file, fbuf); + rprintf(FERROR_XFER, + "skipping symlink with 0-length value: %s\n", + full_fname(fbuf)); + return NULL; + } + } else { + symlink_name = NULL; + symlink_len = 0; + } +#endif + +#ifdef ICONV_OPTION + if (ic_send != (iconv_t)-1) { + xbuf outbuf, inbuf; + + INIT_CONST_XBUF(outbuf, fbuf); + + if (file->dirname) { + INIT_XBUF_STRLEN(inbuf, (char*)file->dirname); + outbuf.size -= 2; /* Reserve room for '/' & 1 more char. */ + if (iconvbufs(ic_send, &inbuf, &outbuf, ICB_INIT) < 0) + goto convert_error; + outbuf.size += 2; + fbuf[outbuf.len++] = '/'; + } + + INIT_XBUF_STRLEN(inbuf, (char*)file->basename); + if (iconvbufs(ic_send, &inbuf, &outbuf, ICB_INIT) < 0) { + convert_error: + io_error |= IOERR_GENERAL; + rprintf(FERROR_XFER, + "[%s] cannot convert filename: %s (%s)\n", + who_am_i(), f_name(file, fbuf), strerror(errno)); + return NULL; + } + fbuf[outbuf.len] = '\0'; + +#ifdef SUPPORT_LINKS + if (symlink_len && sender_symlink_iconv) { + INIT_XBUF(inbuf, (char*)symlink_name, symlink_len, (size_t)-1); + INIT_CONST_XBUF(outbuf, symlink_buf); + if (iconvbufs(ic_send, &inbuf, &outbuf, ICB_INIT) < 0) { + io_error |= IOERR_GENERAL; + f_name(file, fbuf); + rprintf(FERROR_XFER, + "[%s] cannot convert symlink data for: %s (%s)\n", + who_am_i(), full_fname(fbuf), strerror(errno)); + return NULL; + } + symlink_buf[outbuf.len] = '\0'; + + symlink_name = symlink_buf; + symlink_len = outbuf.len; + } +#endif + } else +#endif + f_name(file, fbuf); + +#ifdef SUPPORT_ACLS + if (preserve_acls && !S_ISLNK(file->mode)) { + sx.st.st_mode = file->mode; + if (get_acl(fname, &sx) < 0) { + io_error |= IOERR_GENERAL; + return NULL; + } + } +#endif +#ifdef SUPPORT_XATTRS + if (preserve_xattrs) { + sx.st.st_mode = file->mode; + if (get_xattr(fname, &sx) < 0) { + io_error |= IOERR_GENERAL; + return NULL; + } + } +#endif + + send_file_entry(f, fbuf, file, +#ifdef SUPPORT_LINKS + symlink_name, symlink_len, +#endif + flist->used, flist->ndx_start); + +#ifdef SUPPORT_ACLS + if (preserve_acls && !S_ISLNK(file->mode)) { + send_acl(f, &sx); + free_acl(&sx); + } +#endif +#ifdef SUPPORT_XATTRS + if (preserve_xattrs) { + F_XATTR(file) = send_xattr(f, &sx); + free_xattr(&sx); + } +#endif + } + + maybe_emit_filelist_progress(flist->used + flist_count_offset); + + flist_expand(flist, 1); + flist->files[flist->used++] = file; + + return file; +} + +static void send_if_directory(int f, struct file_list *flist, + struct file_struct *file, + char *fbuf, unsigned int ol, + int flags) +{ + char is_dot_dir = fbuf[ol-1] == '.' && (ol == 1 || fbuf[ol-2] == '/'); + + if (S_ISDIR(file->mode) + && !(file->flags & FLAG_MOUNT_DIR) && f_name(file, fbuf)) { + void *save_filters; + unsigned int len = strlen(fbuf); + if (len > 1 && fbuf[len-1] == '/') + fbuf[--len] = '\0'; + save_filters = push_local_filters(fbuf, len); + send_directory(f, flist, fbuf, len, flags); + pop_local_filters(save_filters); + fbuf[ol] = '\0'; + if (is_dot_dir) + fbuf[ol-1] = '.'; + } +} + +static int file_compare(const void *file1, const void *file2) +{ + return f_name_cmp(*(struct file_struct **)file1, + *(struct file_struct **)file2); +} + +/* The guts of a merge-sort algorithm. This was derived from the glibc + * version, but I (Wayne) changed the merge code to do less copying and + * to require only half the amount of temporary memory. */ +static void fsort_tmp(struct file_struct **fp, size_t num, + struct file_struct **tmp) +{ + struct file_struct **f1, **f2, **t; + size_t n1, n2; + + n1 = num / 2; + n2 = num - n1; + f1 = fp; + f2 = fp + n1; + + if (n1 > 1) + fsort_tmp(f1, n1, tmp); + if (n2 > 1) + fsort_tmp(f2, n2, tmp); + + while (f_name_cmp(*f1, *f2) <= 0) { + if (!--n1) + return; + f1++; + } + + t = tmp; + memcpy(t, f1, n1 * PTR_SIZE); + + *f1++ = *f2++, n2--; + + while (n1 > 0 && n2 > 0) { + if (f_name_cmp(*t, *f2) <= 0) + *f1++ = *t++, n1--; + else + *f1++ = *f2++, n2--; + } + + if (n1 > 0) + memcpy(f1, t, n1 * PTR_SIZE); +} + +/* This file-struct sorting routine makes sure that any identical names in + * the file list stay in the same order as they were in the original list. + * This is particularly vital in inc_recurse mode where we expect a sort + * on the flist to match the exact order of a sort on the dir_flist. */ +static void fsort(struct file_struct **fp, size_t num) +{ + if (num <= 1) + return; + + if (use_qsort) + qsort(fp, num, PTR_SIZE, file_compare); + else { + struct file_struct **tmp = new_array(struct file_struct *, + (num+1) / 2); + fsort_tmp(fp, num, tmp); + free(tmp); + } +} + +/* We take an entire set of sibling dirs from the sorted flist and link them + * into the tree, setting the appropriate parent/child/sibling pointers. */ +static void add_dirs_to_tree(int parent_ndx, struct file_list *from_flist, + int dir_cnt) +{ + int i; + int32 *dp = NULL; + int32 *parent_dp = parent_ndx < 0 ? NULL + : F_DIR_NODE_P(dir_flist->sorted[parent_ndx]); + + flist_expand(dir_flist, dir_cnt); + dir_flist->sorted = dir_flist->files; + + for (i = 0; dir_cnt; i++) { + struct file_struct *file = from_flist->sorted[i]; + + if (!S_ISDIR(file->mode)) + continue; + + dir_flist->files[dir_flist->used++] = file; + dir_cnt--; + + if (file->basename[0] == '.' && file->basename[1] == '\0') + continue; + + if (dp) + DIR_NEXT_SIBLING(dp) = dir_flist->used - 1; + else if (parent_dp) + DIR_FIRST_CHILD(parent_dp) = dir_flist->used - 1; + else + send_dir_ndx = dir_flist->used - 1; + + dp = F_DIR_NODE_P(file); + DIR_PARENT(dp) = parent_ndx; + DIR_FIRST_CHILD(dp) = -1; + } + if (dp) + DIR_NEXT_SIBLING(dp) = -1; +} + +static void interpret_stat_error(const char *fname, int is_dir) +{ + if (errno == ENOENT) { + io_error |= IOERR_VANISHED; + rprintf(FWARNING, "%s has vanished: %s\n", + is_dir ? "directory" : "file", full_fname(fname)); + } else { + io_error |= IOERR_GENERAL; + rsyserr(FERROR_XFER, errno, "link_stat %s failed", + full_fname(fname)); + } +} + +/* This function is normally called by the sender, but the receiving side also + * calls it from get_dirlist() with f set to -1 so that we just construct the + * file list in memory without sending it over the wire. Also, get_dirlist() + * might call this with f set to -2, which also indicates that local filter + * rules should be ignored. */ +static void send_directory(int f, struct file_list *flist, char *fbuf, int len, + int flags) +{ + struct dirent *di; + unsigned remainder; + char *p; + DIR *d; + int divert_dirs = (flags & FLAG_DIVERT_DIRS) != 0; + int start = flist->used; + int filter_level = f == -2 ? SERVER_FILTERS : ALL_FILTERS; + + assert(flist != NULL); + + if (!(d = opendir(fbuf))) { + if (errno == ENOENT) { + if (am_sender) /* Can abuse this for vanished error w/ENOENT: */ + interpret_stat_error(fbuf, True); + return; + } + io_error |= IOERR_GENERAL; + rsyserr(FERROR_XFER, errno, "opendir %s failed", full_fname(fbuf)); + return; + } + + p = fbuf + len; + if (len == 1 && *fbuf == '/') + remainder = MAXPATHLEN - 1; + else if (len < MAXPATHLEN-1) { + *p++ = '/'; + *p = '\0'; + remainder = MAXPATHLEN - (len + 1); + } else + remainder = 0; + + for (errno = 0, di = readdir(d); di; errno = 0, di = readdir(d)) { + unsigned name_len; + char *dname = d_name(di); + if (dname[0] == '.' && (dname[1] == '\0' + || (dname[1] == '.' && dname[2] == '\0'))) + continue; + name_len = strlcpy(p, dname, remainder); + if (name_len >= remainder) { + char save = fbuf[len]; + fbuf[len] = '\0'; + io_error |= IOERR_GENERAL; + rprintf(FERROR_XFER, + "filename overflows max-path len by %u: %s/%s\n", + name_len - remainder + 1, fbuf, dname); + fbuf[len] = save; + continue; + } + if (dname[0] == '\0') { + io_error |= IOERR_GENERAL; + rprintf(FERROR_XFER, + "cannot send file with empty name in %s\n", + full_fname(fbuf)); + continue; + } + + send_file_name(f, flist, fbuf, NULL, flags, filter_level); + } + + fbuf[len] = '\0'; + + if (errno) { + io_error |= IOERR_GENERAL; + rsyserr(FERROR_XFER, errno, "readdir(%s)", full_fname(fbuf)); + } + + closedir(d); + + if (f >= 0 && recurse && !divert_dirs) { + int i, end = flist->used - 1; + /* send_if_directory() bumps flist->used, so use "end". */ + for (i = start; i <= end; i++) + send_if_directory(f, flist, flist->files[i], fbuf, len, flags); + } +} + +static void send_implied_dirs(int f, struct file_list *flist, char *fname, + char *start, char *limit, int flags, char name_type) +{ + static char lastpath[MAXPATHLEN] = ""; + static int lastpath_len = 0; + static struct file_struct *lastpath_struct = NULL; + struct file_struct *file; + item_list *relname_list; + relnamecache **rnpp; + int len, need_new_dir, depth = 0; + filter_rule_list save_filter_list = filter_list; + + flags = (flags | FLAG_IMPLIED_DIR) & ~(FLAG_TOP_DIR | FLAG_CONTENT_DIR); + filter_list.head = filter_list.tail = NULL; /* Don't filter implied dirs. */ + + if (inc_recurse) { + if (lastpath_struct && F_PATHNAME(lastpath_struct) == pathname + && lastpath_len == limit - fname + && strncmp(lastpath, fname, lastpath_len) == 0) + need_new_dir = 0; + else + need_new_dir = 1; + } else { + char *tp = fname, *lp = lastpath; + /* Skip any initial directories in our path that we + * have in common with lastpath. */ + assert(start == fname); + for ( ; ; tp++, lp++) { + if (tp == limit) { + if (*lp == '/' || *lp == '\0') + goto done; + break; + } + if (*lp != *tp) + break; + if (*tp == '/') { + start = tp; + depth++; + } + } + need_new_dir = 1; + } + + if (need_new_dir) { + int save_copy_links = copy_links; + int save_xfer_dirs = xfer_dirs; + char *slash; + + copy_links = xfer_dirs = 1; + + *limit = '\0'; + + for (slash = start; (slash = strchr(slash+1, '/')) != NULL; ) { + *slash = '\0'; + file = send_file_name(f, flist, fname, NULL, flags, ALL_FILTERS); + depth++; + if (!inc_recurse && file && S_ISDIR(file->mode)) + change_local_filter_dir(fname, strlen(fname), depth); + *slash = '/'; + } + + file = send_file_name(f, flist, fname, NULL, flags, ALL_FILTERS); + if (inc_recurse) { + if (file && !S_ISDIR(file->mode)) + file = NULL; + lastpath_struct = file; + } else if (file && S_ISDIR(file->mode)) + change_local_filter_dir(fname, strlen(fname), ++depth); + + strlcpy(lastpath, fname, sizeof lastpath); + lastpath_len = limit - fname; + + *limit = '/'; + + copy_links = save_copy_links; + xfer_dirs = save_xfer_dirs; + + if (!inc_recurse) + goto done; + } + + if (!lastpath_struct) + goto done; /* dir must have vanished */ + + len = strlen(limit+1); + memcpy(&relname_list, F_DIR_RELNAMES_P(lastpath_struct), sizeof relname_list); + if (!relname_list) { + if (!(relname_list = new0(item_list))) + out_of_memory("send_implied_dirs"); + memcpy(F_DIR_RELNAMES_P(lastpath_struct), &relname_list, sizeof relname_list); + } + rnpp = EXPAND_ITEM_LIST(relname_list, relnamecache *, 32); + if (!(*rnpp = (relnamecache*)new_array(char, sizeof (relnamecache) + len))) + out_of_memory("send_implied_dirs"); + (*rnpp)->name_type = name_type; + strlcpy((*rnpp)->fname, limit+1, len + 1); + +done: + filter_list = save_filter_list; +} + +static NORETURN void fatal_unsafe_io_error(void) +{ + /* This (sadly) can only happen when pushing data because + * the sender does not know about what kind of delete + * is in effect on the receiving side when pulling. */ + rprintf(FERROR_XFER, "FATAL I/O ERROR: dying to avoid a --delete-%s issue with a pre-3.0.7 receiver.\n", + delete_during == 2 ? "delay" : "during"); + exit_cleanup(RERR_UNSUPPORTED); +} + +static void send1extra(int f, struct file_struct *file, struct file_list *flist) +{ + char fbuf[MAXPATHLEN]; + item_list *relname_list; + int len, dlen, flags = FLAG_DIVERT_DIRS | FLAG_CONTENT_DIR; + size_t j; + + f_name(file, fbuf); + dlen = strlen(fbuf); + + if (!change_pathname(file, NULL, 0)) + exit_cleanup(RERR_FILESELECT); + + change_local_filter_dir(fbuf, dlen, send_dir_depth); + + if (file->flags & FLAG_CONTENT_DIR) { + if (one_file_system) { + STRUCT_STAT st; + if (link_stat(fbuf, &st, copy_dirlinks) != 0) { + interpret_stat_error(fbuf, True); + return; + } + filesystem_dev = st.st_dev; + } + send_directory(f, flist, fbuf, dlen, flags); + } + + if (!relative_paths) + return; + + memcpy(&relname_list, F_DIR_RELNAMES_P(file), sizeof relname_list); + if (!relname_list) + return; + + for (j = 0; j < relname_list->count; j++) { + char *slash; + relnamecache *rnp = ((relnamecache**)relname_list->items)[j]; + char name_type = rnp->name_type; + + fbuf[dlen] = '/'; + len = strlcpy(fbuf + dlen + 1, rnp->fname, sizeof fbuf - dlen - 1); + free(rnp); + if (len >= (int)sizeof fbuf) + continue; /* Impossible... */ + + slash = strchr(fbuf+dlen+1, '/'); + if (slash) { + send_implied_dirs(f, flist, fbuf, fbuf+dlen+1, slash, flags, name_type); + continue; + } + + if (name_type != NORMAL_NAME) { + STRUCT_STAT st; + if (name_type == MISSING_NAME) + memset(&st, 0, sizeof st); + else if (link_stat(fbuf, &st, 1) != 0) { + interpret_stat_error(fbuf, True); + continue; + } + send_file_name(f, flist, fbuf, &st, FLAG_TOP_DIR | flags, ALL_FILTERS); + } else + send_file_name(f, flist, fbuf, NULL, FLAG_TOP_DIR | flags, ALL_FILTERS); + } + + free(relname_list); +} + +void send_extra_file_list(int f, int at_least) +{ + struct file_list *flist; + int64 start_write; + uint16 prev_flags; + int save_io_error = io_error; + + if (flist_eof) + return; + + if (at_least < 0) + at_least = file_total - file_old_total + 1; + + /* Keep sending data until we have the requested number of + * files in the upcoming file-lists. */ + while (file_total - file_old_total < at_least) { + struct file_struct *file = dir_flist->sorted[send_dir_ndx]; + int dir_ndx, dstart = stats.num_dirs; + const char *pathname = F_PATHNAME(file); + int32 *dp; + + flist = flist_new(0, "send_extra_file_list"); + start_write = stats.total_written; + + if (unsort_ndx) + dir_ndx = F_NDX(file); + else + dir_ndx = send_dir_ndx; + write_ndx(f, NDX_FLIST_OFFSET - dir_ndx); + flist->parent_ndx = dir_ndx; + + send1extra(f, file, flist); + prev_flags = file->flags; + dp = F_DIR_NODE_P(file); + + /* If there are any duplicate directory names that follow, we + * send all the dirs together in one file-list. The dir_flist + * tree links all the child subdirs onto the last dup dir. */ + while ((dir_ndx = DIR_NEXT_SIBLING(dp)) >= 0 + && dir_flist->sorted[dir_ndx]->flags & FLAG_DUPLICATE) { + send_dir_ndx = dir_ndx; + file = dir_flist->sorted[dir_ndx]; + /* Try to avoid some duplicate scanning of identical dirs. */ + if (F_PATHNAME(file) == pathname && prev_flags & FLAG_CONTENT_DIR) + file->flags &= ~FLAG_CONTENT_DIR; + send1extra(f, file, flist); + prev_flags = file->flags; + dp = F_DIR_NODE_P(file); + } + + if (io_error == save_io_error || ignore_errors) + write_byte(f, 0); + else if (use_safe_inc_flist) { + write_shortint(f, XMIT_EXTENDED_FLAGS|XMIT_IO_ERROR_ENDLIST); + write_varint(f, io_error); + } else { + if (delete_during) + fatal_unsafe_io_error(); + write_byte(f, 0); + } + + if (need_unsorted_flist) { + if (!(flist->sorted = new_array(struct file_struct *, flist->used))) + out_of_memory("send_extra_file_list"); + memcpy(flist->sorted, flist->files, + flist->used * sizeof (struct file_struct*)); + } else + flist->sorted = flist->files; + + flist_sort_and_clean(flist, 0); + + add_dirs_to_tree(send_dir_ndx, flist, stats.num_dirs - dstart); + flist_done_allocating(flist); + + file_total += flist->used; + stats.flist_size += stats.total_written - start_write; + stats.num_files += flist->used; + if (DEBUG_GTE(FLIST, 3)) + output_flist(flist); + + if (DIR_FIRST_CHILD(dp) >= 0) { + send_dir_ndx = DIR_FIRST_CHILD(dp); + send_dir_depth++; + } else { + while (DIR_NEXT_SIBLING(dp) < 0) { + if ((send_dir_ndx = DIR_PARENT(dp)) < 0) { + write_ndx(f, NDX_FLIST_EOF); + flist_eof = 1; + if (DEBUG_GTE(FLIST, 3)) + rprintf(FINFO, "[%s] flist_eof=1\n", who_am_i()); + change_local_filter_dir(NULL, 0, 0); + goto finish; + } + send_dir_depth--; + file = dir_flist->sorted[send_dir_ndx]; + dp = F_DIR_NODE_P(file); + } + send_dir_ndx = DIR_NEXT_SIBLING(dp); + } + } + + finish: + if (io_error != save_io_error && protocol_version == 30 && !ignore_errors) + send_msg_int(MSG_IO_ERROR, io_error); +} + +struct file_list *send_file_list(int f, int argc, char *argv[]) +{ + static const char *lastdir; + static int lastdir_len = -1; + int len, dirlen; + STRUCT_STAT st; + char *p, *dir; + struct file_list *flist; + struct timeval start_tv, end_tv; + int64 start_write; + int use_ff_fd = 0; + int disable_buffering, reenable_multiplex = -1; + int flags = recurse ? FLAG_CONTENT_DIR : 0; + int reading_remotely = filesfrom_host != NULL; + int rl_flags = (reading_remotely ? 0 : RL_DUMP_COMMENTS) +#ifdef ICONV_OPTION + | (filesfrom_convert ? RL_CONVERT : 0) +#endif + | (eol_nulls || reading_remotely ? RL_EOL_NULLS : 0); + int implied_dot_dir = 0; + + rprintf(FLOG, "building file list\n"); + if (show_filelist_p()) + start_filelist_progress("building file list"); + else if (inc_recurse && INFO_GTE(FLIST, 1) && !am_server) + rprintf(FCLIENT, "sending incremental file list\n"); + + start_write = stats.total_written; + gettimeofday(&start_tv, NULL); + + if (relative_paths && protocol_version >= 30) + implied_dirs = 1; /* We send flagged implied dirs */ + +#ifdef SUPPORT_HARD_LINKS + if (preserve_hard_links && protocol_version >= 30 && !cur_flist) + init_hard_links(); +#endif + + flist = cur_flist = flist_new(0, "send_file_list"); + if (inc_recurse) { + dir_flist = flist_new(FLIST_TEMP, "send_file_list"); + flags |= FLAG_DIVERT_DIRS; + } else + dir_flist = cur_flist; + + disable_buffering = io_start_buffering_out(f); + if (filesfrom_fd >= 0) { + if (argv[0] && !change_dir(argv[0], CD_NORMAL)) { + rsyserr(FERROR_XFER, errno, "change_dir %s failed", + full_fname(argv[0])); + exit_cleanup(RERR_FILESELECT); + } + if (protocol_version < 31) { + /* Older protocols send the files-from data w/o packaging + * it in multiplexed I/O packets, so temporarily switch + * to buffered I/O to match this behavior. */ + reenable_multiplex = io_end_multiplex_in(MPLX_TO_BUFFERED); + } + use_ff_fd = 1; + } + + if (!orig_dir) + orig_dir = strdup(curr_dir); + + while (1) { + char fbuf[MAXPATHLEN], *fn, name_type; + + if (use_ff_fd) { + if (read_line(filesfrom_fd, fbuf, sizeof fbuf, rl_flags) == 0) + break; + sanitize_path(fbuf, fbuf, "", 0, SP_KEEP_DOT_DIRS); + } else { + if (argc-- == 0) + break; + strlcpy(fbuf, *argv++, MAXPATHLEN); + if (sanitize_paths) + sanitize_path(fbuf, fbuf, "", 0, SP_KEEP_DOT_DIRS); + } + + len = strlen(fbuf); + if (relative_paths) { + /* We clean up fbuf below. */ + name_type = NORMAL_NAME; + } else if (!len || fbuf[len - 1] == '/') { + if (len == 2 && fbuf[0] == '.') { + /* Turn "./" into just "." rather than "./." */ + fbuf[--len] = '\0'; + } else { + if (len + 1 >= MAXPATHLEN) + overflow_exit("send_file_list"); + fbuf[len++] = '.'; + fbuf[len] = '\0'; + } + name_type = DOTDIR_NAME; + } else if (len > 1 && fbuf[len-1] == '.' && fbuf[len-2] == '.' + && (len == 2 || fbuf[len-3] == '/')) { + if (len + 2 >= MAXPATHLEN) + overflow_exit("send_file_list"); + fbuf[len++] = '/'; + fbuf[len++] = '.'; + fbuf[len] = '\0'; + name_type = DOTDIR_NAME; + } else if (fbuf[len-1] == '.' && (len == 1 || fbuf[len-2] == '/')) + name_type = DOTDIR_NAME; + else + name_type = NORMAL_NAME; + + dir = NULL; + + if (!relative_paths) { + p = strrchr(fbuf, '/'); + if (p) { + *p = '\0'; + if (p == fbuf) + dir = "/"; + else + dir = fbuf; + len -= p - fbuf + 1; + fn = p + 1; + } else + fn = fbuf; + } else { + if ((p = strstr(fbuf, "/./")) != NULL) { + *p = '\0'; + if (p == fbuf) + dir = "/"; + else { + dir = fbuf; + clean_fname(dir, 0); + } + fn = p + 3; + while (*fn == '/') + fn++; + if (!*fn) + *--fn = '\0'; /* ensure room for '.' */ + } else + fn = fbuf; + /* A leading ./ can be used in relative mode to affect + * the dest dir without its name being in the path. */ + if (*fn == '.' && fn[1] == '/' && fn[2] && !implied_dot_dir) + implied_dot_dir = -1; + len = clean_fname(fn, CFN_KEEP_TRAILING_SLASH + | CFN_DROP_TRAILING_DOT_DIR); + if (len == 1) { + if (fn[0] == '/') { + fn = "/."; + len = 2; + name_type = DOTDIR_NAME; + } else if (fn[0] == '.') + name_type = DOTDIR_NAME; + } else if (fn[len-1] == '/') { + fn[--len] = '\0'; + if (len == 1 && *fn == '.') + name_type = DOTDIR_NAME; + else + name_type = SLASH_ENDING_NAME; + } + /* Reject a ".." dir in the active part of the path. */ + for (p = fn; (p = strstr(p, "..")) != NULL; p += 2) { + if ((p[2] == '/' || p[2] == '\0') + && (p == fn || p[-1] == '/')) { + rprintf(FERROR, + "found \"..\" dir in relative path: %s\n", + fn); + exit_cleanup(RERR_SYNTAX); + } + } + } + + if (!*fn) { + len = 1; + fn = "."; + name_type = DOTDIR_NAME; + } + + dirlen = dir ? strlen(dir) : 0; + if (dirlen != lastdir_len || memcmp(lastdir, dir, dirlen) != 0) { + if (!change_pathname(NULL, dir, -dirlen)) + goto bad_path; + lastdir = pathname; + lastdir_len = pathname_len; + } else if (!change_pathname(NULL, lastdir, lastdir_len)) { + bad_path: + if (implied_dot_dir < 0) + implied_dot_dir = 0; + continue; + } + + if (implied_dot_dir < 0) { + implied_dot_dir = 1; + send_file_name(f, flist, ".", NULL, (flags | FLAG_IMPLIED_DIR) & ~FLAG_CONTENT_DIR, ALL_FILTERS); + } + + if (fn != fbuf) + memmove(fbuf, fn, len + 1); + + if (link_stat(fbuf, &st, copy_dirlinks || name_type != NORMAL_NAME) != 0 + || (name_type != DOTDIR_NAME && is_daemon_excluded(fbuf, S_ISDIR(st.st_mode))) + || (relative_paths && path_is_daemon_excluded(fbuf, 1))) { + if (errno != ENOENT || missing_args == 0) { + /* This is a transfer error, but inhibit deletion + * only if we might be omitting an existing file. */ + if (errno != ENOENT) + io_error |= IOERR_GENERAL; + rsyserr(FERROR_XFER, errno, "link_stat %s failed", + full_fname(fbuf)); + continue; + } else if (missing_args == 1) { + /* Just ignore the arg. */ + continue; + } else /* (missing_args == 2) */ { + /* Send the arg as a "missing" entry with + * mode 0, which tells the generator to delete it. */ + memset(&st, 0, sizeof st); + } + } + + /* A dot-dir should not be excluded! */ + if (name_type != DOTDIR_NAME && st.st_mode != 0 + && is_excluded(fbuf, S_ISDIR(st.st_mode) != 0, ALL_FILTERS)) + continue; + + if (S_ISDIR(st.st_mode) && !xfer_dirs) { + rprintf(FINFO, "skipping directory %s\n", fbuf); + continue; + } + + if (inc_recurse && relative_paths && *fbuf) { + if ((p = strchr(fbuf+1, '/')) != NULL) { + if (p - fbuf == 1 && *fbuf == '.') { + if ((fn = strchr(p+1, '/')) != NULL) + p = fn; + } else + fn = p; + send_implied_dirs(f, flist, fbuf, fbuf, p, flags, + IS_MISSING_FILE(st) ? MISSING_NAME : name_type); + if (fn == p) + continue; + } + } else if (implied_dirs && (p=strrchr(fbuf,'/')) && p != fbuf) { + /* Send the implied directories at the start of the + * source spec, so we get their permissions right. */ + send_implied_dirs(f, flist, fbuf, fbuf, p, flags, 0); + } + + if (one_file_system) + filesystem_dev = st.st_dev; + + if (recurse || (xfer_dirs && name_type != NORMAL_NAME)) { + struct file_struct *file; + file = send_file_name(f, flist, fbuf, &st, + FLAG_TOP_DIR | FLAG_CONTENT_DIR | flags, + NO_FILTERS); + if (!file) + continue; + if (inc_recurse) { + if (name_type == DOTDIR_NAME) { + if (send_dir_depth < 0) { + send_dir_depth = 0; + change_local_filter_dir(fbuf, len, send_dir_depth); + } + send_directory(f, flist, fbuf, len, flags); + } + } else + send_if_directory(f, flist, file, fbuf, len, flags); + } else + send_file_name(f, flist, fbuf, &st, flags, NO_FILTERS); + } + + if (reenable_multiplex >= 0) + io_start_multiplex_in(reenable_multiplex); + + gettimeofday(&end_tv, NULL); + stats.flist_buildtime = (int64)(end_tv.tv_sec - start_tv.tv_sec) * 1000 + + (end_tv.tv_usec - start_tv.tv_usec) / 1000; + if (stats.flist_buildtime == 0) + stats.flist_buildtime = 1; + start_tv = end_tv; + + /* Indicate end of file list */ + if (io_error == 0 || ignore_errors) + write_byte(f, 0); + else if (use_safe_inc_flist) { + write_shortint(f, XMIT_EXTENDED_FLAGS|XMIT_IO_ERROR_ENDLIST); + write_varint(f, io_error); + } else { + if (delete_during && inc_recurse) + fatal_unsafe_io_error(); + write_byte(f, 0); + } + +#ifdef SUPPORT_HARD_LINKS + if (preserve_hard_links && protocol_version >= 30 && !inc_recurse) + idev_destroy(); +#endif + + if (show_filelist_p()) + finish_filelist_progress(flist); + + gettimeofday(&end_tv, NULL); + stats.flist_xfertime = (int64)(end_tv.tv_sec - start_tv.tv_sec) * 1000 + + (end_tv.tv_usec - start_tv.tv_usec) / 1000; + + /* When converting names, both sides keep an unsorted file-list array + * because the names will differ on the sending and receiving sides + * (both sides will use the unsorted index number for each item). */ + + /* Sort the list without removing any duplicates. This allows the + * receiving side to ask for whatever name it kept. For incremental + * recursion mode, the sender marks duplicate dirs so that it can + * send them together in a single file-list. */ + if (need_unsorted_flist) { + if (!(flist->sorted = new_array(struct file_struct *, flist->used))) + out_of_memory("send_file_list"); + memcpy(flist->sorted, flist->files, + flist->used * sizeof (struct file_struct*)); + } else + flist->sorted = flist->files; + flist_sort_and_clean(flist, 0); + file_total += flist->used; + file_old_total += flist->used; + + if (numeric_ids <= 0 && !inc_recurse) + send_id_list(f); + + /* send the io_error flag */ + if (protocol_version < 30) + write_int(f, ignore_errors ? 0 : io_error); + else if (!use_safe_inc_flist && io_error && !ignore_errors) + send_msg_int(MSG_IO_ERROR, io_error); + + if (disable_buffering) + io_end_buffering_out(IOBUF_FREE_BUFS); + + stats.flist_size = stats.total_written - start_write; + stats.num_files = flist->used; + + if (DEBUG_GTE(FLIST, 3)) + output_flist(flist); + + if (DEBUG_GTE(FLIST, 2)) + rprintf(FINFO, "send_file_list done\n"); + + if (inc_recurse) { + send_dir_depth = 1; + add_dirs_to_tree(-1, flist, stats.num_dirs); + if (!file_total || strcmp(flist->sorted[flist->low]->basename, ".") != 0) + flist->parent_ndx = -1; + flist_done_allocating(flist); + if (send_dir_ndx < 0) { + write_ndx(f, NDX_FLIST_EOF); + flist_eof = 1; + if (DEBUG_GTE(FLIST, 3)) + rprintf(FINFO, "[%s] flist_eof=1\n", who_am_i()); + } + else if (file_total == 1) { + /* If we're creating incremental file-lists and there + * was just 1 item in the first file-list, send 1 more + * file-list to check if this is a 1-file xfer. */ + send_extra_file_list(f, 1); + } + } else { + flist_eof = 1; + if (DEBUG_GTE(FLIST, 3)) + rprintf(FINFO, "[%s] flist_eof=1\n", who_am_i()); + } + + return flist; +} + +struct file_list *recv_file_list(int f) +{ + struct file_list *flist; + int dstart, flags; + int64 start_read; + + if (!first_flist) { + if (show_filelist_p()) + start_filelist_progress("receiving file list"); + else if (inc_recurse && INFO_GTE(FLIST, 1) && !am_server) + rprintf(FCLIENT, "receiving incremental file list\n"); + rprintf(FLOG, "receiving file list\n"); + if (usermap) + parse_name_map(usermap, True); + if (groupmap) + parse_name_map(groupmap, False); + } + + start_read = stats.total_read; + +#ifdef SUPPORT_HARD_LINKS + if (preserve_hard_links && !first_flist) + init_hard_links(); +#endif + + flist = flist_new(0, "recv_file_list"); + + if (inc_recurse) { + if (flist->ndx_start == 1) + dir_flist = flist_new(FLIST_TEMP, "recv_file_list"); + dstart = dir_flist->used; + } else { + dir_flist = flist; + dstart = 0; + } + + while ((flags = read_byte(f)) != 0) { + struct file_struct *file; + + if (protocol_version >= 28 && (flags & XMIT_EXTENDED_FLAGS)) + flags |= read_byte(f) << 8; + + if (flags == (XMIT_EXTENDED_FLAGS|XMIT_IO_ERROR_ENDLIST)) { + int err; + if (!use_safe_inc_flist) { + rprintf(FERROR, "Invalid flist flag: %x\n", flags); + exit_cleanup(RERR_PROTOCOL); + } + err = read_varint(f); + if (!ignore_errors) + io_error |= err; + break; + } + + flist_expand(flist, 1); + file = recv_file_entry(f, flist, flags); + + if (S_ISREG(file->mode)) { + /* Already counted */ + } else if (S_ISDIR(file->mode)) { + if (inc_recurse) { + flist_expand(dir_flist, 1); + dir_flist->files[dir_flist->used++] = file; + } + stats.num_dirs++; + } else if (S_ISLNK(file->mode)) + stats.num_symlinks++; + else if (IS_DEVICE(file->mode)) + stats.num_symlinks++; + else + stats.num_specials++; + + flist->files[flist->used++] = file; + + maybe_emit_filelist_progress(flist->used); + + if (DEBUG_GTE(FLIST, 2)) { + char *name = f_name(file, NULL); + rprintf(FINFO, "recv_file_name(%s)\n", NS(name)); + } + } + file_total += flist->used; + + if (DEBUG_GTE(FLIST, 2)) + rprintf(FINFO, "received %d names\n", flist->used); + + if (show_filelist_p()) + finish_filelist_progress(flist); + + if (need_unsorted_flist) { + /* Create an extra array of index pointers that we can sort for + * the generator's use (for wading through the files in sorted + * order and for calling flist_find()). We keep the "files" + * list unsorted for our exchange of index numbers with the + * other side (since their names may not sort the same). */ + if (!(flist->sorted = new_array(struct file_struct *, flist->used))) + out_of_memory("recv_file_list"); + memcpy(flist->sorted, flist->files, + flist->used * sizeof (struct file_struct*)); + if (inc_recurse && dir_flist->used > dstart) { + static int dir_flist_malloced = 0; + if (dir_flist_malloced < dir_flist->malloced) { + dir_flist->sorted = realloc_array(dir_flist->sorted, + struct file_struct *, + dir_flist->malloced); + dir_flist_malloced = dir_flist->malloced; + } + memcpy(dir_flist->sorted + dstart, dir_flist->files + dstart, + (dir_flist->used - dstart) * sizeof (struct file_struct*)); + fsort(dir_flist->sorted + dstart, dir_flist->used - dstart); + } + } else { + flist->sorted = flist->files; + if (inc_recurse && dir_flist->used > dstart) { + dir_flist->sorted = dir_flist->files; + fsort(dir_flist->sorted + dstart, dir_flist->used - dstart); + } + } + + if (inc_recurse) + flist_done_allocating(flist); + else if (f >= 0) { + recv_id_list(f, flist); + flist_eof = 1; + if (DEBUG_GTE(FLIST, 3)) + rprintf(FINFO, "[%s] flist_eof=1\n", who_am_i()); + } + + /* The --relative option sends paths with a leading slash, so we need + * to specify the strip_root option here. We rejected leading slashes + * for a non-relative transfer in recv_file_entry(). */ + flist_sort_and_clean(flist, relative_paths); + + if (protocol_version < 30) { + /* Recv the io_error flag */ + int err = read_int(f); + if (!ignore_errors) + io_error |= err; + } else if (inc_recurse && flist->ndx_start == 1) { + if (!file_total || strcmp(flist->sorted[flist->low]->basename, ".") != 0) + flist->parent_ndx = -1; + } + + if (DEBUG_GTE(FLIST, 3)) + output_flist(flist); + + if (DEBUG_GTE(FLIST, 2)) + rprintf(FINFO, "recv_file_list done\n"); + + stats.flist_size += stats.total_read - start_read; + stats.num_files += flist->used; + + return flist; +} + +/* This is only used once by the receiver if the very first file-list + * has exactly one item in it. */ +void recv_additional_file_list(int f) +{ + struct file_list *flist; + int ndx = read_ndx(f); + if (ndx == NDX_FLIST_EOF) { + flist_eof = 1; + if (DEBUG_GTE(FLIST, 3)) + rprintf(FINFO, "[%s] flist_eof=1\n", who_am_i()); + change_local_filter_dir(NULL, 0, 0); + } else { + ndx = NDX_FLIST_OFFSET - ndx; + if (ndx < 0 || ndx >= dir_flist->used) { + ndx = NDX_FLIST_OFFSET - ndx; + rprintf(FERROR, + "[%s] Invalid dir index: %d (%d - %d)\n", + who_am_i(), ndx, NDX_FLIST_OFFSET, + NDX_FLIST_OFFSET - dir_flist->used + 1); + exit_cleanup(RERR_PROTOCOL); + } + if (DEBUG_GTE(FLIST, 3)) { + rprintf(FINFO, "[%s] receiving flist for dir %d\n", + who_am_i(), ndx); + } + flist = recv_file_list(f); + flist->parent_ndx = ndx; + } +} + +/* Search for an identically-named item in the file list. Note that the + * items must agree in their directory-ness, or no match is returned. */ +int flist_find(struct file_list *flist, struct file_struct *f) +{ + int low = flist->low, high = flist->high; + int diff, mid, mid_up; + + while (low <= high) { + mid = (low + high) / 2; + if (F_IS_ACTIVE(flist->sorted[mid])) + mid_up = mid; + else { + /* Scan for the next non-empty entry using the cached + * distance values. If the value isn't fully up-to- + * date, update it. */ + mid_up = mid + F_DEPTH(flist->sorted[mid]); + if (!F_IS_ACTIVE(flist->sorted[mid_up])) { + do { + mid_up += F_DEPTH(flist->sorted[mid_up]); + } while (!F_IS_ACTIVE(flist->sorted[mid_up])); + F_DEPTH(flist->sorted[mid]) = mid_up - mid; + } + if (mid_up > high) { + /* If there's nothing left above us, set high to + * a non-empty entry below us and continue. */ + high = mid - (int)flist->sorted[mid]->len32; + if (!F_IS_ACTIVE(flist->sorted[high])) { + do { + high -= (int)flist->sorted[high]->len32; + } while (!F_IS_ACTIVE(flist->sorted[high])); + flist->sorted[mid]->len32 = mid - high; + } + continue; + } + } + diff = f_name_cmp(flist->sorted[mid_up], f); + if (diff == 0) { + if (protocol_version < 29 + && S_ISDIR(flist->sorted[mid_up]->mode) + != S_ISDIR(f->mode)) + return -1; + return mid_up; + } + if (diff < 0) + low = mid_up + 1; + else + high = mid - 1; + } + return -1; +} + +/* Search for an identically-named item in the file list. Differs from + * flist_find in that an item that agrees with "f" in directory-ness is + * preferred but one that does not is still found. */ +int flist_find_ignore_dirness(struct file_list *flist, struct file_struct *f) +{ + mode_t save_mode; + int ndx; + + /* First look for an item that agrees in directory-ness. */ + ndx = flist_find(flist, f); + if (ndx >= 0) + return ndx; + + /* Temporarily flip f->mode to look for an item of opposite + * directory-ness. */ + save_mode = f->mode; + f->mode = S_ISDIR(f->mode) ? S_IFREG : S_IFDIR; + ndx = flist_find(flist, f); + f->mode = save_mode; + return ndx; +} + +/* + * Free up any resources a file_struct has allocated + * and clear the file. + */ +void clear_file(struct file_struct *file) +{ + /* The +1 zeros out the first char of the basename. */ + memset(file, 0, FILE_STRUCT_LEN + 1); + /* In an empty entry, F_DEPTH() is an offset to the next non-empty + * entry. Likewise for len32 in the opposite direction. We assume + * that we're alone for now since flist_find() will adjust the counts + * it runs into that aren't up-to-date. */ + file->len32 = F_DEPTH(file) = 1; +} + +/* Allocate a new file list. */ +struct file_list *flist_new(int flags, char *msg) +{ + struct file_list *flist; + + if (!(flist = new0(struct file_list))) + out_of_memory(msg); + + if (flags & FLIST_TEMP) { + if (!(flist->file_pool = pool_create(SMALL_EXTENT, 0, + out_of_memory, + POOL_INTERN))) + out_of_memory(msg); + } else { + /* This is a doubly linked list with prev looping back to + * the end of the list, but the last next pointer is NULL. */ + if (!first_flist) { + flist->file_pool = pool_create(NORMAL_EXTENT, 0, + out_of_memory, + POOL_INTERN); + if (!flist->file_pool) + out_of_memory(msg); + + flist->ndx_start = flist->flist_num = inc_recurse ? 1 : 0; + + first_flist = cur_flist = flist->prev = flist; + } else { + struct file_list *prev = first_flist->prev; + + flist->file_pool = first_flist->file_pool; + + flist->ndx_start = prev->ndx_start + prev->used + 1; + flist->flist_num = prev->flist_num + 1; + + flist->prev = prev; + prev->next = first_flist->prev = flist; + } + flist->pool_boundary = pool_boundary(flist->file_pool, 0); + flist_cnt++; + } + + return flist; +} + +/* Free up all elements in a flist. */ +void flist_free(struct file_list *flist) +{ + if (!flist->prev) { + /* Was FLIST_TEMP dir-list. */ + } else if (flist == flist->prev) { + first_flist = cur_flist = NULL; + file_total = 0; + flist_cnt = 0; + } else { + if (flist == cur_flist) + cur_flist = flist->next; + if (flist == first_flist) + first_flist = first_flist->next; + else { + flist->prev->next = flist->next; + if (!flist->next) + flist->next = first_flist; + } + flist->next->prev = flist->prev; + file_total -= flist->used; + flist_cnt--; + } + + if (!flist->prev || !flist_cnt) + pool_destroy(flist->file_pool); + else + pool_free_old(flist->file_pool, flist->pool_boundary); + + if (flist->sorted && flist->sorted != flist->files) + free(flist->sorted); + free(flist->files); + free(flist); +} + +/* This routine ensures we don't have any duplicate names in our file list. + * duplicate names can cause corruption because of the pipelining. */ +static void flist_sort_and_clean(struct file_list *flist, int strip_root) +{ + char fbuf[MAXPATHLEN]; + int i, prev_i; + + if (!flist) + return; + if (flist->used == 0) { + flist->high = -1; + flist->low = 0; + return; + } + + fsort(flist->sorted, flist->used); + + if (!am_sender || inc_recurse) { + for (i = prev_i = 0; i < flist->used; i++) { + if (F_IS_ACTIVE(flist->sorted[i])) { + prev_i = i; + break; + } + } + flist->low = prev_i; + } else { + i = prev_i = flist->used - 1; + flist->low = 0; + } + + while (++i < flist->used) { + int j; + struct file_struct *file = flist->sorted[i]; + + if (!F_IS_ACTIVE(file)) + continue; + if (f_name_cmp(file, flist->sorted[prev_i]) == 0) + j = prev_i; + else if (protocol_version >= 29 && S_ISDIR(file->mode)) { + int save_mode = file->mode; + /* Make sure that this directory doesn't duplicate a + * non-directory earlier in the list. */ + flist->high = prev_i; + file->mode = S_IFREG; + j = flist_find(flist, file); + file->mode = save_mode; + } else + j = -1; + if (j >= 0) { + int keep, drop; + /* If one is a dir and the other is not, we want to + * keep the dir because it might have contents in the + * list. Otherwise keep the first one. */ + if (S_ISDIR(file->mode)) { + struct file_struct *fp = flist->sorted[j]; + if (!S_ISDIR(fp->mode)) + keep = i, drop = j; + else { + if (am_sender) + file->flags |= FLAG_DUPLICATE; + else { /* Make sure we merge our vital flags. */ + fp->flags |= file->flags & (FLAG_TOP_DIR|FLAG_CONTENT_DIR); + fp->flags &= file->flags | ~FLAG_IMPLIED_DIR; + } + keep = j, drop = i; + } + } else + keep = j, drop = i; + + if (!am_sender) { + if (DEBUG_GTE(DUP, 1)) { + rprintf(FINFO, + "removing duplicate name %s from file list (%d)\n", + f_name(file, fbuf), drop + flist->ndx_start); + } + clear_file(flist->sorted[drop]); + } + + if (keep == i) { + if (flist->low == drop) { + for (j = drop + 1; + j < i && !F_IS_ACTIVE(flist->sorted[j]); + j++) {} + flist->low = j; + } + prev_i = i; + } + } else + prev_i = i; + } + flist->high = prev_i; + + if (strip_root) { + /* We need to strip off the leading slashes for relative + * paths, but this must be done _after_ the sorting phase. */ + for (i = flist->low; i <= flist->high; i++) { + struct file_struct *file = flist->sorted[i]; + + if (!file->dirname) + continue; + while (*file->dirname == '/') + file->dirname++; + if (!*file->dirname) + file->dirname = NULL; + } + } + + if (prune_empty_dirs && !am_sender) { + int j, prev_depth = 0; + + prev_i = 0; /* It's OK that this isn't really true. */ + + for (i = flist->low; i <= flist->high; i++) { + struct file_struct *fp, *file = flist->sorted[i]; + + /* This temporarily abuses the F_DEPTH() value for a + * directory that is in a chain that might get pruned. + * We restore the old value if it gets a reprieve. */ + if (S_ISDIR(file->mode) && F_DEPTH(file)) { + /* Dump empty dirs when coming back down. */ + for (j = prev_depth; j >= F_DEPTH(file); j--) { + fp = flist->sorted[prev_i]; + if (F_DEPTH(fp) >= 0) + break; + prev_i = -F_DEPTH(fp)-1; + clear_file(fp); + } + prev_depth = F_DEPTH(file); + if (is_excluded(f_name(file, fbuf), 1, + ALL_FILTERS)) { + /* Keep dirs through this dir. */ + for (j = prev_depth-1; ; j--) { + fp = flist->sorted[prev_i]; + if (F_DEPTH(fp) >= 0) + break; + prev_i = -F_DEPTH(fp)-1; + F_DEPTH(fp) = j; + } + } else + F_DEPTH(file) = -prev_i-1; + prev_i = i; + } else { + /* Keep dirs through this non-dir. */ + for (j = prev_depth; ; j--) { + fp = flist->sorted[prev_i]; + if (F_DEPTH(fp) >= 0) + break; + prev_i = -F_DEPTH(fp)-1; + F_DEPTH(fp) = j; + } + } + } + /* Dump all remaining empty dirs. */ + while (1) { + struct file_struct *fp = flist->sorted[prev_i]; + if (F_DEPTH(fp) >= 0) + break; + prev_i = -F_DEPTH(fp)-1; + clear_file(fp); + } + + for (i = flist->low; i <= flist->high; i++) { + if (F_IS_ACTIVE(flist->sorted[i])) + break; + } + flist->low = i; + for (i = flist->high; i >= flist->low; i--) { + if (F_IS_ACTIVE(flist->sorted[i])) + break; + } + flist->high = i; + } +} + +static void output_flist(struct file_list *flist) +{ + char uidbuf[16], gidbuf[16], depthbuf[16]; + struct file_struct *file; + const char *root, *dir, *slash, *name, *trail; + const char *who = who_am_i(); + int i; + + rprintf(FINFO, "[%s] flist start=%d, used=%d, low=%d, high=%d\n", + who, flist->ndx_start, flist->used, flist->low, flist->high); + for (i = 0; i < flist->used; i++) { + file = flist->files[i]; + if ((am_root || am_sender) && uid_ndx) { + snprintf(uidbuf, sizeof uidbuf, " uid=%u", + F_OWNER(file)); + } else + *uidbuf = '\0'; + if (gid_ndx) { + static char parens[] = "(\0)\0\0\0"; + char *pp = parens + (file->flags & FLAG_SKIP_GROUP ? 0 : 3); + snprintf(gidbuf, sizeof gidbuf, " gid=%s%u%s", + pp, F_GROUP(file), pp + 2); + } else + *gidbuf = '\0'; + if (!am_sender) + snprintf(depthbuf, sizeof depthbuf, "%d", F_DEPTH(file)); + if (F_IS_ACTIVE(file)) { + root = am_sender ? NS(F_PATHNAME(file)) : depthbuf; + if ((dir = file->dirname) == NULL) + dir = slash = ""; + else + slash = "/"; + name = file->basename; + trail = S_ISDIR(file->mode) ? "/" : ""; + } else + root = dir = slash = name = trail = ""; + rprintf(FINFO, + "[%s] i=%d %s %s%s%s%s mode=0%o len=%s%s%s flags=%x\n", + who, i + flist->ndx_start, + root, dir, slash, name, trail, + (int)file->mode, comma_num(F_LENGTH(file)), + uidbuf, gidbuf, file->flags); + } +} + +enum fnc_state { s_DIR, s_SLASH, s_BASE, s_TRAILING }; +enum fnc_type { t_PATH, t_ITEM }; + +static int found_prefix; + +/* Compare the names of two file_struct entities, similar to how strcmp() + * would do if it were operating on the joined strings. + * + * Some differences beginning with protocol_version 29: (1) directory names + * are compared with an assumed trailing slash so that they compare in a + * way that would cause them to sort immediately prior to any content they + * may have; (2) a directory of any name compares after a non-directory of + * any name at the same depth; (3) a directory with name "." compares prior + * to anything else. These changes mean that a directory and a non-dir + * with the same name will not compare as equal (protocol_version >= 29). + * + * The dirname component can be an empty string, but the basename component + * cannot (and never is in the current codebase). The basename component + * may be NULL (for a removed item), in which case it is considered to be + * after any existing item. */ +int f_name_cmp(const struct file_struct *f1, const struct file_struct *f2) +{ + int dif; + const uchar *c1, *c2; + enum fnc_state state1, state2; + enum fnc_type type1, type2; + enum fnc_type t_path = protocol_version >= 29 ? t_PATH : t_ITEM; + + if (!f1 || !F_IS_ACTIVE(f1)) { + if (!f2 || !F_IS_ACTIVE(f2)) + return 0; + return -1; + } + if (!f2 || !F_IS_ACTIVE(f2)) + return 1; + + c1 = (uchar*)f1->dirname; + c2 = (uchar*)f2->dirname; + if (c1 == c2) + c1 = c2 = NULL; + if (!c1) { + type1 = S_ISDIR(f1->mode) ? t_path : t_ITEM; + c1 = (const uchar*)f1->basename; + if (type1 == t_PATH && *c1 == '.' && !c1[1]) { + type1 = t_ITEM; + state1 = s_TRAILING; + c1 = (uchar*)""; + } else + state1 = s_BASE; + } else { + type1 = t_path; + state1 = s_DIR; + } + if (!c2) { + type2 = S_ISDIR(f2->mode) ? t_path : t_ITEM; + c2 = (const uchar*)f2->basename; + if (type2 == t_PATH && *c2 == '.' && !c2[1]) { + type2 = t_ITEM; + state2 = s_TRAILING; + c2 = (uchar*)""; + } else + state2 = s_BASE; + } else { + type2 = t_path; + state2 = s_DIR; + } + + if (type1 != type2) + return type1 == t_PATH ? 1 : -1; + + do { + if (!*c1) { + switch (state1) { + case s_DIR: + state1 = s_SLASH; + c1 = (uchar*)"/"; + break; + case s_SLASH: + type1 = S_ISDIR(f1->mode) ? t_path : t_ITEM; + c1 = (const uchar*)f1->basename; + if (type1 == t_PATH && *c1 == '.' && !c1[1]) { + type1 = t_ITEM; + state1 = s_TRAILING; + c1 = (uchar*)""; + } else + state1 = s_BASE; + break; + case s_BASE: + state1 = s_TRAILING; + if (type1 == t_PATH) { + c1 = (uchar*)"/"; + break; + } + /* FALL THROUGH */ + case s_TRAILING: + type1 = t_ITEM; + break; + } + if (*c2 && type1 != type2) + return type1 == t_PATH ? 1 : -1; + } + if (!*c2) { + switch (state2) { + case s_DIR: + state2 = s_SLASH; + c2 = (uchar*)"/"; + break; + case s_SLASH: + type2 = S_ISDIR(f2->mode) ? t_path : t_ITEM; + c2 = (const uchar*)f2->basename; + if (type2 == t_PATH && *c2 == '.' && !c2[1]) { + type2 = t_ITEM; + state2 = s_TRAILING; + c2 = (uchar*)""; + } else + state2 = s_BASE; + break; + case s_BASE: + state2 = s_TRAILING; + if (type2 == t_PATH) { + c2 = (uchar*)"/"; + break; + } + /* FALL THROUGH */ + case s_TRAILING: + found_prefix = 1; + if (!*c1) + return 0; + type2 = t_ITEM; + break; + } + if (type1 != type2) + return type1 == t_PATH ? 1 : -1; + } + } while ((dif = (int)*c1++ - (int)*c2++) == 0); + + return dif; +} + +/* Returns 1 if f1's filename has all of f2's filename as a prefix. This does + * not match if f2's basename is not an exact match of a path element in f1. + * E.g. /path/foo is not a prefix of /path/foobar/baz, but /path/foobar is. */ +int f_name_has_prefix(const struct file_struct *f1, const struct file_struct *f2) +{ + found_prefix = 0; + f_name_cmp(f1, f2); + return found_prefix; +} + +char *f_name_buf(void) +{ + static char names[5][MAXPATHLEN]; + static unsigned int n; + + n = (n + 1) % (sizeof names / sizeof names[0]); + + return names[n]; +} + +/* Return a copy of the full filename of a flist entry, using the indicated + * buffer or one of 5 static buffers if fbuf is NULL. No size-checking is + * done because we checked the size when creating the file_struct entry. + */ +char *f_name(const struct file_struct *f, char *fbuf) +{ + if (!f || !F_IS_ACTIVE(f)) + return NULL; + + if (!fbuf) + fbuf = f_name_buf(); + + if (f->dirname) { + int len = strlen(f->dirname); + memcpy(fbuf, f->dirname, len); + fbuf[len] = '/'; + strlcpy(fbuf + len + 1, f->basename, MAXPATHLEN - (len + 1)); + } else + strlcpy(fbuf, f->basename, MAXPATHLEN); + + return fbuf; +} + +/* Do a non-recursive scan of the named directory, possibly ignoring all + * exclude rules except for the daemon's. If "dlen" is >=0, it is the length + * of the dirname string, and also indicates that "dirname" is a MAXPATHLEN + * buffer (the functions we call will append names onto the end, but the old + * dir value will be restored on exit). */ +struct file_list *get_dirlist(char *dirname, int dlen, int flags) +{ + struct file_list *dirlist; + char dirbuf[MAXPATHLEN]; + int save_recurse = recurse; + int save_xfer_dirs = xfer_dirs; + int save_prune_empty_dirs = prune_empty_dirs; + int senddir_fd = flags & GDL_IGNORE_FILTER_RULES ? -2 : -1; + + if (dlen < 0) { + dlen = strlcpy(dirbuf, dirname, MAXPATHLEN); + if (dlen >= MAXPATHLEN) + return NULL; + dirname = dirbuf; + } + + dirlist = flist_new(FLIST_TEMP, "get_dirlist"); + + recurse = 0; + xfer_dirs = 1; + send_directory(senddir_fd, dirlist, dirname, dlen, FLAG_CONTENT_DIR); + xfer_dirs = save_xfer_dirs; + recurse = save_recurse; + if (INFO_GTE(PROGRESS, 1)) + flist_count_offset += dirlist->used; + + prune_empty_dirs = 0; + dirlist->sorted = dirlist->files; + flist_sort_and_clean(dirlist, 0); + prune_empty_dirs = save_prune_empty_dirs; + + if (DEBUG_GTE(FLIST, 3)) + output_flist(dirlist); + + return dirlist; +} diff --git a/rsync/generator.c b/rsync/generator.c new file mode 100644 index 0000000..91009a5 --- /dev/null +++ b/rsync/generator.c @@ -0,0 +1,2370 @@ +/* + * Routines that are exclusive to the generator process. + * + * Copyright (C) 1996-2000 Andrew Tridgell + * Copyright (C) 1996 Paul Mackerras + * Copyright (C) 2002 Martin Pool + * Copyright (C) 2003-2014 Wayne Davison + * + * 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, visit the http://fsf.org website. + */ + +#include "rsync.h" +#include "inums.h" +#include "ifuncs.h" + +extern int dry_run; +extern int do_xfers; +extern int stdout_format_has_i; +extern int logfile_format_has_i; +extern int am_root; +extern int am_server; +extern int am_daemon; +extern int inc_recurse; +extern int relative_paths; +extern int implied_dirs; +extern int keep_dirlinks; +extern int preserve_acls; +extern int preserve_xattrs; +extern int preserve_links; +extern int preserve_devices; +extern int preserve_specials; +extern int preserve_hard_links; +extern int preserve_executability; +extern int preserve_perms; +extern int preserve_times; +extern int delete_mode; +extern int delete_before; +extern int delete_during; +extern int delete_after; +extern int missing_args; +extern int msgdone_cnt; +extern int ignore_errors; +extern int remove_source_files; +extern int delay_updates; +extern int update_only; +extern int human_readable; +extern int ignore_existing; +extern int ignore_non_existing; +extern int want_xattr_optim; +extern int inplace; +extern int append_mode; +extern int make_backups; +extern int csum_length; +extern int ignore_times; +extern int size_only; +extern OFF_T max_size; +extern OFF_T min_size; +extern int io_error; +extern int flist_eof; +extern int allowed_lull; +extern int sock_f_out; +extern int protocol_version; +extern int file_total; +extern int fuzzy_basis; +extern int always_checksum; +extern int checksum_len; +extern char *partial_dir; +extern int compare_dest; +extern int copy_dest; +extern int link_dest; +extern int whole_file; +extern int list_only; +extern int read_batch; +extern int write_batch; +extern int safe_symlinks; +extern long block_size; /* "long" because popt can't set an int32. */ +extern int unsort_ndx; +extern int max_delete; +extern int force_delete; +extern int one_file_system; +extern int skipped_deletes; +extern dev_t filesystem_dev; +extern mode_t orig_umask; +extern uid_t our_uid; +extern char *tmpdir; +extern char *basis_dir[MAX_BASIS_DIRS+1]; +extern struct file_list *cur_flist, *first_flist, *dir_flist; +extern filter_rule_list filter_list, daemon_filter_list; + +int maybe_ATTRS_REPORT = 0; + +static dev_t dev_zero; +static int deldelay_size = 0, deldelay_cnt = 0; +static char *deldelay_buf = NULL; +static int deldelay_fd = -1; +static int loopchk_limit; +static int dir_tweaking; +static int symlink_timeset_failed_flags; +static int need_retouch_dir_times; +static int need_retouch_dir_perms; +static const char *solo_file = NULL; + +enum nonregtype { + TYPE_DIR, TYPE_SPECIAL, TYPE_DEVICE, TYPE_SYMLINK +}; + +/* Forward declarations. */ +#ifdef SUPPORT_HARD_LINKS +static void handle_skipped_hlink(struct file_struct *file, int itemizing, + enum logcode code, int f_out); +#endif + +#define EARLY_DELAY_DONE_MSG() (!delay_updates) +#define EARLY_DELETE_DONE_MSG() (!(delete_during == 2 || delete_after)) + +static int start_delete_delay_temp(void) +{ + char fnametmp[MAXPATHLEN]; + int save_dry_run = dry_run; + + dry_run = 0; + if (!get_tmpname(fnametmp, "deldelay", False) + || (deldelay_fd = do_mkstemp(fnametmp, 0600)) < 0) { + rprintf(FINFO, "NOTE: Unable to create delete-delay temp file%s.\n", + inc_recurse ? "" : " -- switching to --delete-after"); + delete_during = 0; + delete_after = !inc_recurse; + dry_run = save_dry_run; + return 0; + } + unlink(fnametmp); + dry_run = save_dry_run; + return 1; +} + +static int flush_delete_delay(void) +{ + if (deldelay_fd < 0 && !start_delete_delay_temp()) + return 0; + if (write(deldelay_fd, deldelay_buf, deldelay_cnt) != deldelay_cnt) { + rsyserr(FERROR, errno, "flush of delete-delay buffer"); + delete_during = 0; + delete_after = !inc_recurse; + close(deldelay_fd); + return 0; + } + deldelay_cnt = 0; + return 1; +} + +static int remember_delete(struct file_struct *file, const char *fname, int flags) +{ + int len; + + if (deldelay_cnt == deldelay_size && !flush_delete_delay()) + return 0; + + if (flags & DEL_NO_UID_WRITE) + deldelay_buf[deldelay_cnt++] = '!'; + + while (1) { + len = snprintf(deldelay_buf + deldelay_cnt, + deldelay_size - deldelay_cnt, + "%x %s%c", + (int)file->mode, fname, '\0'); + if ((deldelay_cnt += len) <= deldelay_size) + break; + deldelay_cnt -= len; + if (!flush_delete_delay()) + return 0; + } + + return 1; +} + +static int read_delay_line(char *buf, int *flags_p) +{ + static int read_pos = 0; + int j, len, mode; + char *bp, *past_space; + + while (1) { + for (j = read_pos; j < deldelay_cnt && deldelay_buf[j]; j++) {} + if (j < deldelay_cnt) + break; + if (deldelay_fd < 0) { + if (j > read_pos) + goto invalid_data; + return -1; + } + deldelay_cnt -= read_pos; + if (deldelay_cnt == deldelay_size) + goto invalid_data; + if (deldelay_cnt && read_pos) { + memmove(deldelay_buf, deldelay_buf + read_pos, + deldelay_cnt); + } + len = read(deldelay_fd, deldelay_buf + deldelay_cnt, + deldelay_size - deldelay_cnt); + if (len == 0) { + if (deldelay_cnt) { + rprintf(FERROR, + "ERROR: unexpected EOF in delete-delay file.\n"); + } + return -1; + } + if (len < 0) { + rsyserr(FERROR, errno, + "reading delete-delay file"); + return -1; + } + deldelay_cnt += len; + read_pos = 0; + } + + bp = deldelay_buf + read_pos; + if (*bp == '!') { + bp++; + *flags_p = DEL_NO_UID_WRITE; + } else + *flags_p = 0; + + if (sscanf(bp, "%x ", &mode) != 1) { + invalid_data: + rprintf(FERROR, "ERROR: invalid data in delete-delay file.\n"); + return -1; + } + past_space = strchr(bp, ' ') + 1; + len = j - read_pos - (past_space - bp) + 1; /* count the '\0' */ + read_pos = j + 1; + + if (len > MAXPATHLEN) { + rprintf(FERROR, "ERROR: filename too long in delete-delay file.\n"); + return -1; + } + + /* The caller needs the name in a MAXPATHLEN buffer, so we copy it + * instead of returning a pointer to our buffer. */ + memcpy(buf, past_space, len); + + return mode; +} + +static void do_delayed_deletions(char *delbuf) +{ + int mode, flags; + + if (deldelay_fd >= 0) { + if (deldelay_cnt && !flush_delete_delay()) + return; + lseek(deldelay_fd, 0, 0); + } + while ((mode = read_delay_line(delbuf, &flags)) >= 0) + delete_item(delbuf, mode, flags | DEL_RECURSE); + if (deldelay_fd >= 0) + close(deldelay_fd); +} + +/* This function is used to implement per-directory deletion, and is used by + * all the --delete-WHEN options. Note that the fbuf pointer must point to a + * MAXPATHLEN buffer with the name of the directory in it (the functions we + * call will append names onto the end, but the old dir value will be restored + * on exit). */ +static void delete_in_dir(char *fbuf, struct file_struct *file, dev_t *fs_dev) +{ + static int already_warned = 0; + struct file_list *dirlist; + char delbuf[MAXPATHLEN]; + int dlen, i; + + if (!fbuf) { + change_local_filter_dir(NULL, 0, 0); + return; + } + + if (DEBUG_GTE(DEL, 2)) + rprintf(FINFO, "delete_in_dir(%s)\n", fbuf); + + if (allowed_lull) + maybe_send_keepalive(time(NULL), MSK_ALLOW_FLUSH); + + if (io_error & IOERR_GENERAL && !ignore_errors) { + if (already_warned) + return; + rprintf(FINFO, + "IO error encountered -- skipping file deletion\n"); + already_warned = 1; + return; + } + + dlen = strlen(fbuf); + change_local_filter_dir(fbuf, dlen, F_DEPTH(file)); + + if (one_file_system) { + if (file->flags & FLAG_TOP_DIR) + filesystem_dev = *fs_dev; + else if (filesystem_dev != *fs_dev) + return; + } + + dirlist = get_dirlist(fbuf, dlen, 0); + + /* If an item in dirlist is not found in flist, delete it + * from the filesystem. */ + for (i = dirlist->used; i--; ) { + struct file_struct *fp = dirlist->files[i]; + if (!F_IS_ACTIVE(fp)) + continue; + if (fp->flags & FLAG_MOUNT_DIR && S_ISDIR(fp->mode)) { + if (INFO_GTE(MOUNT, 1)) + rprintf(FINFO, "cannot delete mount point: %s\n", + f_name(fp, NULL)); + continue; + } + /* Here we want to match regardless of file type. Replacement + * of a file with one of another type is handled separately by + * a delete_item call with a DEL_MAKE_ROOM flag. */ + if (flist_find_ignore_dirness(cur_flist, fp) < 0) { + int flags = DEL_RECURSE; + if (!(fp->mode & S_IWUSR) && !am_root && fp->flags & FLAG_OWNED_BY_US) + flags |= DEL_NO_UID_WRITE; + f_name(fp, delbuf); + if (delete_during == 2) { + if (!remember_delete(fp, delbuf, flags)) + break; + } else + delete_item(delbuf, fp->mode, flags); + } + } + + flist_free(dirlist); +} + +/* This deletes any files on the receiving side that are not present on the + * sending side. This is used by --delete-before and --delete-after. */ +static void do_delete_pass(void) +{ + char fbuf[MAXPATHLEN]; + STRUCT_STAT st; + int j; + + /* dry_run is incremented when the destination doesn't exist yet. */ + if (dry_run > 1 || list_only) + return; + + for (j = 0; j < cur_flist->used; j++) { + struct file_struct *file = cur_flist->sorted[j]; + + if (!F_IS_ACTIVE(file)) + continue; + + f_name(file, fbuf); + + if (!(file->flags & FLAG_CONTENT_DIR)) { + change_local_filter_dir(fbuf, strlen(fbuf), F_DEPTH(file)); + continue; + } + + if (DEBUG_GTE(DEL, 1) && file->flags & FLAG_TOP_DIR) + rprintf(FINFO, "deleting in %s\n", fbuf); + + if (link_stat(fbuf, &st, keep_dirlinks) < 0 + || !S_ISDIR(st.st_mode)) + continue; + + delete_in_dir(fbuf, file, &st.st_dev); + } + delete_in_dir(NULL, NULL, &dev_zero); + + if (INFO_GTE(FLIST, 2) && !am_server) + rprintf(FINFO, " \r"); +} + +static inline int time_differs(struct file_struct *file, stat_x *sxp) +{ + return cmp_time(sxp->st.st_mtime, file->modtime); +} + +static inline int perms_differ(struct file_struct *file, stat_x *sxp) +{ + if (preserve_perms) + return !BITS_EQUAL(sxp->st.st_mode, file->mode, CHMOD_BITS); + + if (preserve_executability) + return (sxp->st.st_mode & 0111 ? 1 : 0) ^ (file->mode & 0111 ? 1 : 0); + + return 0; +} + +static inline int ownership_differs(struct file_struct *file, stat_x *sxp) +{ + if (am_root && uid_ndx && sxp->st.st_uid != (uid_t)F_OWNER(file)) + return 1; + + if (gid_ndx && !(file->flags & FLAG_SKIP_GROUP) && sxp->st.st_gid != (gid_t)F_GROUP(file)) + return 1; + + return 0; +} + +#ifdef SUPPORT_ACLS +static inline int acls_differ(const char *fname, struct file_struct *file, stat_x *sxp) +{ + if (preserve_acls) { + if (!ACL_READY(*sxp)) + get_acl(fname, sxp); + if (set_acl(NULL, file, sxp, file->mode)) + return 1; + } + + return 0; +} +#endif + +#ifdef SUPPORT_XATTRS +static inline int xattrs_differ(const char *fname, struct file_struct *file, stat_x *sxp) +{ + if (preserve_xattrs) { + if (!XATTR_READY(*sxp)) + get_xattr(fname, sxp); + if (xattr_diff(file, sxp, 0)) + return 1; + } + + return 0; +} +#endif + +int unchanged_attrs(const char *fname, struct file_struct *file, stat_x *sxp) +{ + if (S_ISLNK(file->mode)) { +#ifdef CAN_SET_SYMLINK_TIMES + if (preserve_times & PRESERVE_LINK_TIMES && time_differs(file, sxp)) + return 0; +#endif +#ifdef CAN_CHMOD_SYMLINK + if (perms_differ(file, sxp)) + return 0; +#endif +#ifdef CAN_CHOWN_SYMLINK + if (ownership_differs(file, sxp)) + return 0; +#endif +#if defined SUPPORT_ACLS && 0 /* no current symlink-ACL support */ + if (acls_differ(fname, file, sxp)) + return 0; +#endif +#if defined SUPPORT_XATTRS && !defined NO_SYMLINK_XATTRS + if (xattrs_differ(fname, file, sxp)) + return 0; +#endif + } else { + if (preserve_times && time_differs(file, sxp)) + return 0; + if (perms_differ(file, sxp)) + return 0; + if (ownership_differs(file, sxp)) + return 0; +#ifdef SUPPORT_ACLS + if (acls_differ(fname, file, sxp)) + return 0; +#endif +#ifdef SUPPORT_XATTRS + if (xattrs_differ(fname, file, sxp)) + return 0; +#endif + } + + return 1; +} + +void itemize(const char *fnamecmp, struct file_struct *file, int ndx, int statret, + stat_x *sxp, int32 iflags, uchar fnamecmp_type, + const char *xname) +{ + if (statret >= 0) { /* A from-dest-dir statret can == 1! */ + int keep_time = !preserve_times ? 0 + : S_ISDIR(file->mode) ? preserve_times & PRESERVE_DIR_TIMES + : S_ISLNK(file->mode) ? preserve_times & PRESERVE_LINK_TIMES + : 1; + + if (S_ISREG(file->mode) && F_LENGTH(file) != sxp->st.st_size) + iflags |= ITEM_REPORT_SIZE; + if (file->flags & FLAG_TIME_FAILED) { /* symlinks only */ + if (iflags & ITEM_LOCAL_CHANGE) + iflags |= symlink_timeset_failed_flags; + } else if (keep_time + ? cmp_time(file->modtime, sxp->st.st_mtime) != 0 + : iflags & (ITEM_TRANSFER|ITEM_LOCAL_CHANGE) && !(iflags & ITEM_MATCHED) + && (!(iflags & ITEM_XNAME_FOLLOWS) || *xname)) + iflags |= ITEM_REPORT_TIME; +#if !defined HAVE_LCHMOD && !defined HAVE_SETATTRLIST + if (S_ISLNK(file->mode)) { + ; + } else +#endif + if (preserve_perms) { + if (!BITS_EQUAL(sxp->st.st_mode, file->mode, CHMOD_BITS)) + iflags |= ITEM_REPORT_PERMS; + } else if (preserve_executability + && ((sxp->st.st_mode & 0111 ? 1 : 0) ^ (file->mode & 0111 ? 1 : 0))) + iflags |= ITEM_REPORT_PERMS; + if (uid_ndx && am_root && (uid_t)F_OWNER(file) != sxp->st.st_uid) + iflags |= ITEM_REPORT_OWNER; + if (gid_ndx && !(file->flags & FLAG_SKIP_GROUP) + && sxp->st.st_gid != (gid_t)F_GROUP(file)) + iflags |= ITEM_REPORT_GROUP; +#ifdef SUPPORT_ACLS + if (preserve_acls && !S_ISLNK(file->mode)) { + if (!ACL_READY(*sxp)) + get_acl(fnamecmp, sxp); + if (set_acl(NULL, file, sxp, file->mode)) + iflags |= ITEM_REPORT_ACL; + } +#endif +#ifdef SUPPORT_XATTRS + if (preserve_xattrs) { + if (!XATTR_READY(*sxp)) + get_xattr(fnamecmp, sxp); + if (xattr_diff(file, sxp, 1)) + iflags |= ITEM_REPORT_XATTR; + } +#endif + } else { +#ifdef SUPPORT_XATTRS + if (preserve_xattrs && xattr_diff(file, NULL, 1)) + iflags |= ITEM_REPORT_XATTR; +#endif + iflags |= ITEM_IS_NEW; + } + + iflags &= 0xffff; + if ((iflags & (SIGNIFICANT_ITEM_FLAGS|ITEM_REPORT_XATTR) || INFO_GTE(NAME, 2) + || stdout_format_has_i > 1 || (xname && *xname)) && !read_batch) { + if (protocol_version >= 29) { + if (ndx >= 0) + write_ndx(sock_f_out, ndx); + write_shortint(sock_f_out, iflags); + if (iflags & ITEM_BASIS_TYPE_FOLLOWS) + write_byte(sock_f_out, fnamecmp_type); + if (iflags & ITEM_XNAME_FOLLOWS) + write_vstring(sock_f_out, xname, strlen(xname)); +#ifdef SUPPORT_XATTRS + if (preserve_xattrs && do_xfers + && iflags & (ITEM_REPORT_XATTR|ITEM_TRANSFER)) { + int fd = iflags & ITEM_REPORT_XATTR + && !(want_xattr_optim && BITS_SET(iflags, ITEM_XNAME_FOLLOWS|ITEM_LOCAL_CHANGE)) + ? sock_f_out : -1; + send_xattr_request(NULL, file, fd); + } +#endif + } else if (ndx >= 0) { + enum logcode code = logfile_format_has_i ? FINFO : FCLIENT; + log_item(code, file, iflags, xname); + } + } +} + + +/* Perform our quick-check heuristic for determining if a file is unchanged. */ +int unchanged_file(char *fn, struct file_struct *file, STRUCT_STAT *st) +{ + if (st->st_size != F_LENGTH(file)) + return 0; + + /* if always checksum is set then we use the checksum instead + of the file time to determine whether to sync */ + if (always_checksum > 0 && S_ISREG(st->st_mode)) { + char sum[MAX_DIGEST_LEN]; + file_checksum(fn, st, sum); + return memcmp(sum, F_SUM(file), checksum_len) == 0; + } + + if (size_only > 0) + return 1; + + if (ignore_times) + return 0; + + return cmp_time(st->st_mtime, file->modtime) == 0; +} + + +/* + * set (initialize) the size entries in the per-file sum_struct + * calculating dynamic block and checksum sizes. + * + * This is only called from generate_and_send_sums() but is a separate + * function to encapsulate the logic. + * + * The block size is a rounded square root of file length. + * + * The checksum size is determined according to: + * blocksum_bits = BLOCKSUM_BIAS + 2*log2(file_len) - log2(block_len) + * provided by Donovan Baarda which gives a probability of rsync + * algorithm corrupting data and falling back using the whole md4 + * checksums. + * + * This might be made one of several selectable heuristics. + */ +static void sum_sizes_sqroot(struct sum_struct *sum, int64 len) +{ + int32 blength; + int s2length; + int64 l; + + if (len < 0) { + /* The file length overflowed our int64 var, so we can't process this file. */ + sum->count = -1; /* indicate overflow error */ + return; + } + + if (block_size) + blength = block_size; + else if (len <= BLOCK_SIZE * BLOCK_SIZE) + blength = BLOCK_SIZE; + else { + int32 max_blength = protocol_version < 30 ? OLD_MAX_BLOCK_SIZE : MAX_BLOCK_SIZE; + int32 c; + int cnt; + for (c = 1, l = len, cnt = 0; l >>= 2; c <<= 1, cnt++) {} + if (c < 0 || c >= max_blength) + blength = max_blength; + else { + blength = 0; + do { + blength |= c; + if (len < (int64)blength * blength) + blength &= ~c; + c >>= 1; + } while (c >= 8); /* round to multiple of 8 */ + blength = MAX(blength, BLOCK_SIZE); + } + } + + if (protocol_version < 27) { + s2length = csum_length; + } else if (csum_length == SUM_LENGTH) { + s2length = SUM_LENGTH; + } else { + int32 c; + int b = BLOCKSUM_BIAS; + for (l = len; l >>= 1; b += 2) {} + for (c = blength; (c >>= 1) && b; b--) {} + /* add a bit, subtract rollsum, round up. */ + s2length = (b + 1 - 32 + 7) / 8; /* --optimize in compiler-- */ + s2length = MAX(s2length, csum_length); + s2length = MIN(s2length, SUM_LENGTH); + } + + sum->flength = len; + sum->blength = blength; + sum->s2length = s2length; + sum->remainder = (int32)(len % blength); + sum->count = (int32)(l = (len / blength) + (sum->remainder != 0)); + + if ((int64)sum->count != l) + sum->count = -1; + + if (sum->count && DEBUG_GTE(DELTASUM, 2)) { + rprintf(FINFO, + "count=%s rem=%ld blength=%ld s2length=%d flength=%s\n", + big_num(sum->count), (long)sum->remainder, (long)sum->blength, + sum->s2length, big_num(sum->flength)); + } +} + + +/* + * Generate and send a stream of signatures/checksums that describe a buffer + * + * Generate approximately one checksum every block_len bytes. + */ +static int generate_and_send_sums(int fd, OFF_T len, int f_out, int f_copy) +{ + int32 i; + struct map_struct *mapbuf; + struct sum_struct sum; + OFF_T offset = 0; + + sum_sizes_sqroot(&sum, len); + if (sum.count < 0) + return -1; + write_sum_head(f_out, &sum); + + if (append_mode > 0 && f_copy < 0) + return 0; + + if (len > 0) + mapbuf = map_file(fd, len, MAX_MAP_SIZE, sum.blength); + else + mapbuf = NULL; + + for (i = 0; i < sum.count; i++) { + int32 n1 = (int32)MIN(len, (OFF_T)sum.blength); + char *map = map_ptr(mapbuf, offset, n1); + char sum2[SUM_LENGTH]; + uint32 sum1; + + len -= n1; + offset += n1; + + if (f_copy >= 0) { + full_write(f_copy, map, n1); + if (append_mode > 0) + continue; + } + + sum1 = get_checksum1(map, n1); + get_checksum2(map, n1, sum2); + + if (DEBUG_GTE(DELTASUM, 3)) { + rprintf(FINFO, + "chunk[%s] offset=%s len=%ld sum1=%08lx\n", + big_num(i), big_num(offset - n1), (long)n1, + (unsigned long)sum1); + } + write_int(f_out, sum1); + write_buf(f_out, sum2, sum.s2length); + } + + if (mapbuf) + unmap_file(mapbuf); + + return 0; +} + + +/* Try to find a filename in the same dir as "fname" with a similar name. */ +static struct file_struct *find_fuzzy(struct file_struct *file, struct file_list *dirlist_array[], uchar *fnamecmp_type_ptr) +{ + int fname_len, fname_suf_len; + const char *fname_suf, *fname = file->basename; + uint32 lowest_dist = 25 << 16; /* ignore a distance greater than 25 */ + int i, j; + struct file_struct *lowest_fp = NULL; + + fname_len = strlen(fname); + fname_suf = find_filename_suffix(fname, fname_len, &fname_suf_len); + + /* Try to find an exact size+mtime match first. */ + for (i = 0; i < fuzzy_basis; i++) { + struct file_list *dirlist = dirlist_array[i]; + + if (!dirlist) + continue; + + for (j = 0; j < dirlist->used; j++) { + struct file_struct *fp = dirlist->files[j]; + + if (!F_IS_ACTIVE(fp)) + continue; + + if (!S_ISREG(fp->mode) || !F_LENGTH(fp) || fp->flags & FLAG_FILE_SENT) + continue; + + if (F_LENGTH(fp) == F_LENGTH(file) && cmp_time(fp->modtime, file->modtime) == 0) { + if (DEBUG_GTE(FUZZY, 2)) + rprintf(FINFO, "fuzzy size/modtime match for %s\n", f_name(fp, NULL)); + *fnamecmp_type_ptr = FNAMECMP_FUZZY + i; + return fp; + } + + } + } + + for (i = 0; i < fuzzy_basis; i++) { + struct file_list *dirlist = dirlist_array[i]; + + if (!dirlist) + continue; + + for (j = 0; j < dirlist->used; j++) { + struct file_struct *fp = dirlist->files[j]; + const char *suf, *name; + int len, suf_len; + uint32 dist; + + if (!F_IS_ACTIVE(fp)) + continue; + + if (!S_ISREG(fp->mode) || !F_LENGTH(fp) || fp->flags & FLAG_FILE_SENT) + continue; + + name = fp->basename; + len = strlen(name); + suf = find_filename_suffix(name, len, &suf_len); + + dist = fuzzy_distance(name, len, fname, fname_len); + /* Add some extra weight to how well the suffixes match. */ + dist += fuzzy_distance(suf, suf_len, fname_suf, fname_suf_len) * 10; + if (DEBUG_GTE(FUZZY, 2)) { + rprintf(FINFO, "fuzzy distance for %s = %d.%05d\n", + f_name(fp, NULL), (int)(dist>>16), (int)(dist&0xFFFF)); + } + if (dist <= lowest_dist) { + lowest_dist = dist; + lowest_fp = fp; + *fnamecmp_type_ptr = FNAMECMP_FUZZY + i; + } + } + } + + return lowest_fp; +} + +/* Copy a file found in our --copy-dest handling. */ +static int copy_altdest_file(const char *src, const char *dest, struct file_struct *file) +{ + char buf[MAXPATHLEN]; + const char *copy_to, *partialptr; + int save_preserve_xattrs = preserve_xattrs; + int ok, fd_w; + + if (inplace) { + /* Let copy_file open the destination in place. */ + fd_w = -1; + copy_to = dest; + } else { + fd_w = open_tmpfile(buf, dest, file); + if (fd_w < 0) + return -1; + copy_to = buf; + } + cleanup_set(copy_to, NULL, NULL, -1, -1); + if (copy_file(src, copy_to, fd_w, file->mode) < 0) { + if (INFO_GTE(COPY, 1)) { + rsyserr(FINFO, errno, "copy_file %s => %s", + full_fname(src), copy_to); + } + /* Try to clean up. */ + unlink(copy_to); + cleanup_disable(); + return -1; + } + partialptr = partial_dir ? partial_dir_fname(dest) : NULL; + preserve_xattrs = 0; /* xattrs were copied with file */ + ok = finish_transfer(dest, copy_to, src, partialptr, file, 1, 0); + preserve_xattrs = save_preserve_xattrs; + cleanup_disable(); + return ok ? 0 : -1; +} + +/* This is only called for regular files. We return -2 if we've finished + * handling the file, -1 if no dest-linking occurred, or a non-negative + * value if we found an alternate basis file. If we're called with the + * find_exact_for_existing flag, the destination file already exists, so + * we only try to find an exact alt-dest match. In this case, the returns + * are only -2 & -1 (both as above). */ +static int try_dests_reg(struct file_struct *file, char *fname, int ndx, + char *cmpbuf, stat_x *sxp, int find_exact_for_existing, + int itemizing, enum logcode code) +{ + STRUCT_STAT real_st = sxp->st; + int best_match = -1; + int match_level = 0; + int j = 0; + + do { + pathjoin(cmpbuf, MAXPATHLEN, basis_dir[j], fname); + if (link_stat(cmpbuf, &sxp->st, 0) < 0 || !S_ISREG(sxp->st.st_mode)) + continue; + switch (match_level) { + case 0: + best_match = j; + match_level = 1; + /* FALL THROUGH */ + case 1: + if (!unchanged_file(cmpbuf, file, &sxp->st)) + continue; + best_match = j; + match_level = 2; + /* FALL THROUGH */ + case 2: + if (!unchanged_attrs(cmpbuf, file, sxp)) { + free_stat_x(sxp); + continue; + } + best_match = j; + match_level = 3; + break; + } + break; + } while (basis_dir[++j] != NULL); + + if (!match_level) + return -1; + + if (j != best_match) { + j = best_match; + pathjoin(cmpbuf, MAXPATHLEN, basis_dir[j], fname); + if (link_stat(cmpbuf, &sxp->st, 0) < 0) + return -1; + } + + if (match_level == 3 && !copy_dest) { + if (find_exact_for_existing) { + if (link_dest && real_st.st_dev == sxp->st.st_dev && real_st.st_ino == sxp->st.st_ino) + return -1; + if (do_unlink(fname) < 0 && errno != ENOENT) { + sxp->st = real_st; + return -1; + } + } +#ifdef SUPPORT_HARD_LINKS + if (link_dest) { + if (!hard_link_one(file, fname, cmpbuf, 1)) + goto try_a_copy; + if (preserve_hard_links && F_IS_HLINKED(file)) + finish_hard_link(file, fname, ndx, &sxp->st, itemizing, code, j); + if (!maybe_ATTRS_REPORT && (INFO_GTE(NAME, 2) || stdout_format_has_i > 1)) { + itemize(cmpbuf, file, ndx, 1, sxp, + ITEM_LOCAL_CHANGE | ITEM_XNAME_FOLLOWS, + 0, ""); + } + } else +#endif + { + if (itemizing) + itemize(cmpbuf, file, ndx, 0, sxp, 0, 0, NULL); + } + if (INFO_GTE(NAME, 2) && maybe_ATTRS_REPORT) + rprintf(FCLIENT, "%s is uptodate\n", fname); + return -2; + } + + if (find_exact_for_existing) { + sxp->st = real_st; + return -1; + } + + if (match_level >= 2) { +#ifdef SUPPORT_HARD_LINKS + try_a_copy: /* Copy the file locally. */ +#endif + if (!dry_run && copy_altdest_file(cmpbuf, fname, file) < 0) { + if (find_exact_for_existing) /* Can get here via hard-link failure */ + sxp->st = real_st; + return -1; + } + if (itemizing) + itemize(cmpbuf, file, ndx, 0, sxp, ITEM_LOCAL_CHANGE, 0, NULL); + if (maybe_ATTRS_REPORT + && ((!itemizing && INFO_GTE(NAME, 1) && match_level == 2) + || (INFO_GTE(NAME, 2) && match_level == 3))) { + code = match_level == 3 ? FCLIENT : FINFO; + rprintf(code, "%s%s\n", fname, + match_level == 3 ? " is uptodate" : ""); + } +#ifdef SUPPORT_HARD_LINKS + if (preserve_hard_links && F_IS_HLINKED(file)) + finish_hard_link(file, fname, ndx, &sxp->st, itemizing, code, -1); +#endif + return -2; + } + + return FNAMECMP_BASIS_DIR_LOW + j; +} + +/* This is only called for non-regular files. We return -2 if we've finished + * handling the file, or -1 if no dest-linking occurred, or a non-negative + * value if we found an alternate basis file. */ +static int try_dests_non(struct file_struct *file, char *fname, int ndx, + char *cmpbuf, stat_x *sxp, int itemizing, + enum logcode code) +{ + int best_match = -1; + int match_level = 0; + enum nonregtype type; + uint32 *devp; +#ifdef SUPPORT_LINKS + char lnk[MAXPATHLEN]; + int len; +#endif + int j = 0; + +#ifndef SUPPORT_LINKS + if (S_ISLNK(file->mode)) + return -1; +#endif + if (S_ISDIR(file->mode)) { + type = TYPE_DIR; + } else if (IS_SPECIAL(file->mode)) + type = TYPE_SPECIAL; + else if (IS_DEVICE(file->mode)) + type = TYPE_DEVICE; +#ifdef SUPPORT_LINKS + else if (S_ISLNK(file->mode)) + type = TYPE_SYMLINK; +#endif + else { + rprintf(FERROR, + "internal: try_dests_non() called with invalid mode (%o)\n", + (int)file->mode); + exit_cleanup(RERR_UNSUPPORTED); + } + + do { + pathjoin(cmpbuf, MAXPATHLEN, basis_dir[j], fname); + if (link_stat(cmpbuf, &sxp->st, 0) < 0) + continue; + switch (type) { + case TYPE_DIR: + if (!S_ISDIR(sxp->st.st_mode)) + continue; + break; + case TYPE_SPECIAL: + if (!IS_SPECIAL(sxp->st.st_mode)) + continue; + break; + case TYPE_DEVICE: + if (!IS_DEVICE(sxp->st.st_mode)) + continue; + break; + case TYPE_SYMLINK: +#ifdef SUPPORT_LINKS + if (!S_ISLNK(sxp->st.st_mode)) + continue; + break; +#else + return -1; +#endif + } + if (match_level < 1) { + match_level = 1; + best_match = j; + } + switch (type) { + case TYPE_DIR: + case TYPE_SPECIAL: + break; + case TYPE_DEVICE: + devp = F_RDEV_P(file); + if (sxp->st.st_rdev != MAKEDEV(DEV_MAJOR(devp), DEV_MINOR(devp))) + continue; + break; + case TYPE_SYMLINK: +#ifdef SUPPORT_LINKS + if ((len = do_readlink(cmpbuf, lnk, MAXPATHLEN-1)) <= 0) + continue; + lnk[len] = '\0'; + if (strcmp(lnk, F_SYMLINK(file)) != 0) + continue; + break; +#else + return -1; +#endif + } + if (match_level < 2) { + match_level = 2; + best_match = j; + } + if (unchanged_attrs(cmpbuf, file, sxp)) { + match_level = 3; + best_match = j; + break; + } + } while (basis_dir[++j] != NULL); + + if (!match_level) + return -1; + + if (j != best_match) { + j = best_match; + pathjoin(cmpbuf, MAXPATHLEN, basis_dir[j], fname); + if (link_stat(cmpbuf, &sxp->st, 0) < 0) + return -1; + } + + if (match_level == 3) { +#ifdef SUPPORT_HARD_LINKS + if (link_dest +#ifndef CAN_HARDLINK_SYMLINK + && !S_ISLNK(file->mode) +#endif +#ifndef CAN_HARDLINK_SPECIAL + && !IS_SPECIAL(file->mode) && !IS_DEVICE(file->mode) +#endif + && !S_ISDIR(file->mode)) { + if (do_link(cmpbuf, fname) < 0) { + rsyserr(FERROR_XFER, errno, + "failed to hard-link %s with %s", + cmpbuf, fname); + return j; + } + if (preserve_hard_links && F_IS_HLINKED(file)) + finish_hard_link(file, fname, ndx, NULL, itemizing, code, -1); + } else +#endif + match_level = 2; + if (itemizing && stdout_format_has_i + && (INFO_GTE(NAME, 2) || stdout_format_has_i > 1)) { + int chg = compare_dest && type != TYPE_DIR ? 0 + : ITEM_LOCAL_CHANGE + (match_level == 3 ? ITEM_XNAME_FOLLOWS : 0); + char *lp = match_level == 3 ? "" : NULL; + itemize(cmpbuf, file, ndx, 0, sxp, chg + ITEM_MATCHED, 0, lp); + } + if (INFO_GTE(NAME, 2) && maybe_ATTRS_REPORT) { + rprintf(FCLIENT, "%s%s is uptodate\n", + fname, type == TYPE_DIR ? "/" : ""); + } + return -2; + } + + return j; +} + +static void list_file_entry(struct file_struct *f) +{ + char permbuf[PERMSTRING_SIZE]; + int64 len; + int colwidth = human_readable ? 14 : 11; + + if (!F_IS_ACTIVE(f)) { + /* this can happen if duplicate names were removed */ + return; + } + + permstring(permbuf, f->mode); + len = F_LENGTH(f); + + /* TODO: indicate '+' if the entry has an ACL. */ + +#ifdef SUPPORT_LINKS + if (preserve_links && S_ISLNK(f->mode)) { + rprintf(FINFO, "%s %*s %s %s -> %s\n", + permbuf, colwidth, human_num(len), + timestring(f->modtime), f_name(f, NULL), + F_SYMLINK(f)); + } else +#endif + if (missing_args == 2 && f->mode == 0) { + rprintf(FINFO, "%-*s %s\n", + colwidth + 31, "*missing", + f_name(f, NULL)); + } else { + rprintf(FINFO, "%s %*s %s %s\n", + permbuf, colwidth, human_num(len), + timestring(f->modtime), f_name(f, NULL)); + } +} + +static int phase = 0; +static int dflt_perms; + +static int implied_dirs_are_missing; +/* Helper for recv_generator's skip_dir and dry_missing_dir tests. */ +static BOOL is_below(struct file_struct *file, struct file_struct *subtree) +{ + return F_DEPTH(file) > F_DEPTH(subtree) + && (!implied_dirs_are_missing || f_name_has_prefix(file, subtree)); +} + +/* Acts on the indicated item in cur_flist whose name is fname. If a dir, + * make sure it exists, and has the right permissions/timestamp info. For + * all other non-regular files (symlinks, etc.) we create them here. For + * regular files that have changed, we try to find a basis file and then + * start sending checksums. The ndx is the file's unique index value. + * + * The fname parameter must point to a MAXPATHLEN buffer! (e.g it gets + * passed to delete_item(), which can use it during a recursive delete.) + * + * Note that f_out is set to -1 when doing final directory-permission and + * modification-time repair. */ +static void recv_generator(char *fname, struct file_struct *file, int ndx, + int itemizing, enum logcode code, int f_out) +{ + static const char *parent_dirname = ""; + /* Missing dir not created due to --dry-run; will still be scanned. */ + static struct file_struct *dry_missing_dir = NULL; + /* Missing dir whose contents are skipped altogether due to + * --ignore-non-existing, daemon exclude, or mkdir failure. */ + static struct file_struct *skip_dir = NULL; + static struct file_list *fuzzy_dirlist[MAX_BASIS_DIRS+1]; + static int need_fuzzy_dirlist = 0; + struct file_struct *fuzzy_file = NULL; + int fd = -1, f_copy = -1; + stat_x sx, real_sx; + STRUCT_STAT partial_st; + struct file_struct *back_file = NULL; + int statret, real_ret, stat_errno; + char *fnamecmp, *partialptr, *backupptr = NULL; + char fnamecmpbuf[MAXPATHLEN]; + uchar fnamecmp_type; + int del_opts = delete_mode || force_delete ? DEL_RECURSE : 0; + int is_dir = !S_ISDIR(file->mode) ? 0 + : inc_recurse && ndx != cur_flist->ndx_start - 1 ? -1 + : 1; + + if (DEBUG_GTE(GENR, 1)) + rprintf(FINFO, "recv_generator(%s,%d)\n", fname, ndx); + + if (list_only) { + if (is_dir < 0 + || (is_dir && !implied_dirs && file->flags & FLAG_IMPLIED_DIR)) + return; + list_file_entry(file); + return; + } + + if (skip_dir) { + if (is_below(file, skip_dir)) { + if (is_dir) + file->flags |= FLAG_MISSING_DIR; +#ifdef SUPPORT_HARD_LINKS + else if (F_IS_HLINKED(file)) + handle_skipped_hlink(file, itemizing, code, f_out); +#endif + return; + } + skip_dir = NULL; + } + + init_stat_x(&sx); + if (daemon_filter_list.head && (*fname != '.' || fname[1])) { + if (check_filter(&daemon_filter_list, FLOG, fname, is_dir) < 0) { + if (is_dir < 0) + return; +#ifdef SUPPORT_HARD_LINKS + if (F_IS_HLINKED(file)) + handle_skipped_hlink(file, itemizing, code, f_out); +#endif + rprintf(FERROR_XFER, + "ERROR: daemon refused to receive %s \"%s\"\n", + is_dir ? "directory" : "file", fname); + if (is_dir) + goto skipping_dir_contents; + return; + } + } + + if (dry_run > 1 || (dry_missing_dir && is_below(file, dry_missing_dir))) { + int i; + parent_is_dry_missing: + for (i = 0; i < fuzzy_basis; i++) { + if (fuzzy_dirlist[i]) { + flist_free(fuzzy_dirlist[i]); + fuzzy_dirlist[i] = NULL; + } + } + parent_dirname = ""; + statret = -1; + stat_errno = ENOENT; + } else { + const char *dn = file->dirname ? file->dirname : "."; + dry_missing_dir = NULL; + if (parent_dirname != dn && strcmp(parent_dirname, dn) != 0) { + if (relative_paths && !implied_dirs + && do_stat(dn, &sx.st) < 0) { + if (dry_run) + goto parent_is_dry_missing; + if (make_path(fname, MKP_DROP_NAME | MKP_SKIP_SLASH) < 0) { + rsyserr(FERROR_XFER, errno, + "recv_generator: mkdir %s failed", + full_fname(dn)); + } + } + if (fuzzy_basis) { + int i; + for (i = 0; i < fuzzy_basis; i++) { + if (fuzzy_dirlist[i]) { + flist_free(fuzzy_dirlist[i]); + fuzzy_dirlist[i] = NULL; + } + } + need_fuzzy_dirlist = 1; + } +#ifdef SUPPORT_ACLS + if (!preserve_perms) + dflt_perms = default_perms_for_dir(dn); +#endif + } + parent_dirname = dn; + + if (need_fuzzy_dirlist && S_ISREG(file->mode)) { + int i; + strlcpy(fnamecmpbuf, dn, sizeof fnamecmpbuf); + for (i = 0; i < fuzzy_basis; i++) { + if (i && pathjoin(fnamecmpbuf, MAXPATHLEN, basis_dir[i-1], dn) >= MAXPATHLEN) + continue; + fuzzy_dirlist[i] = get_dirlist(fnamecmpbuf, -1, GDL_IGNORE_FILTER_RULES); + if (fuzzy_dirlist[i] && fuzzy_dirlist[i]->used == 0) { + flist_free(fuzzy_dirlist[i]); + fuzzy_dirlist[i] = NULL; + } + } + need_fuzzy_dirlist = 0; + } + + statret = link_stat(fname, &sx.st, keep_dirlinks && is_dir); + stat_errno = errno; + } + + if (missing_args == 2 && file->mode == 0) { + if (filter_list.head && check_filter(&filter_list, FINFO, fname, is_dir) < 0) + return; + if (statret == 0) + delete_item(fname, sx.st.st_mode, del_opts); + return; + } + + if (ignore_non_existing > 0 && statret == -1 && stat_errno == ENOENT) { + if (is_dir) { + if (is_dir < 0) + return; + skip_dir = file; + file->flags |= FLAG_MISSING_DIR; + } +#ifdef SUPPORT_HARD_LINKS + else if (F_IS_HLINKED(file)) + handle_skipped_hlink(file, itemizing, code, f_out); +#endif + if (INFO_GTE(SKIP, 1)) { + rprintf(FINFO, "not creating new %s \"%s\"\n", + is_dir ? "directory" : "file", fname); + } + return; + } + + if (statret == 0 && !(sx.st.st_mode & S_IWUSR) + && !am_root && sx.st.st_uid == our_uid) + del_opts |= DEL_NO_UID_WRITE; + + if (ignore_existing > 0 && statret == 0 + && (!is_dir || !S_ISDIR(sx.st.st_mode))) { + if (INFO_GTE(SKIP, 1) && is_dir >= 0) + rprintf(FINFO, "%s exists\n", fname); +#ifdef SUPPORT_HARD_LINKS + if (F_IS_HLINKED(file)) + handle_skipped_hlink(file, itemizing, code, f_out); +#endif + goto cleanup; + } + + fnamecmp = fname; + + if (is_dir) { + mode_t added_perms; + if (!implied_dirs && file->flags & FLAG_IMPLIED_DIR) + goto cleanup; + if (am_root < 0) { + /* For --fake-super, the dir must be useable by the copying + * user, just like it would be for root. */ + added_perms = S_IRUSR|S_IWUSR|S_IXUSR; + } else + added_perms = 0; + if (is_dir < 0) { + if (!(preserve_times & PRESERVE_DIR_TIMES)) + return; + /* In inc_recurse mode we want to make sure any missing + * directories get created while we're still processing + * the parent dir (which allows us to touch the parent + * dir's mtime right away). We will handle the dir in + * full later (right before we handle its contents). */ + if (statret == 0 + && (S_ISDIR(sx.st.st_mode) + || delete_item(fname, sx.st.st_mode, del_opts | DEL_FOR_DIR) != 0)) + goto cleanup; /* Any errors get reported later. */ + if (do_mkdir(fname, (file->mode|added_perms) & 0700) == 0) + file->flags |= FLAG_DIR_CREATED; + goto cleanup; + } + /* The file to be received is a directory, so we need + * to prepare appropriately. If there is already a + * file of that name and it is *not* a directory, then + * we need to delete it. If it doesn't exist, then + * (perhaps recursively) create it. */ + if (statret == 0 && !S_ISDIR(sx.st.st_mode)) { + if (delete_item(fname, sx.st.st_mode, del_opts | DEL_FOR_DIR) != 0) + goto skipping_dir_contents; + statret = -1; + } + if (dry_run && statret != 0) { + if (!dry_missing_dir) + dry_missing_dir = file; + file->flags |= FLAG_MISSING_DIR; + } + init_stat_x(&real_sx); + real_sx.st = sx.st; + real_ret = statret; + if (file->flags & FLAG_DIR_CREATED) + statret = -1; + if (!preserve_perms) { /* See comment in non-dir code below. */ + file->mode = dest_mode(file->mode, sx.st.st_mode, + dflt_perms, statret == 0); + } + if (statret != 0 && basis_dir[0] != NULL) { + int j = try_dests_non(file, fname, ndx, fnamecmpbuf, &sx, + itemizing, code); + if (j == -2) { + itemizing = 0; + code = FNONE; + statret = 1; + } else if (j >= 0) { + statret = 1; + fnamecmp = fnamecmpbuf; + } + } + if (itemizing && f_out != -1) { + itemize(fnamecmp, file, ndx, statret, &sx, + statret ? ITEM_LOCAL_CHANGE : 0, 0, NULL); + } + if (real_ret != 0 && do_mkdir(fname,file->mode|added_perms) < 0 && errno != EEXIST) { + if (!relative_paths || errno != ENOENT + || make_path(fname, MKP_DROP_NAME | MKP_SKIP_SLASH) < 0 + || (do_mkdir(fname, file->mode|added_perms) < 0 && errno != EEXIST)) { + rsyserr(FERROR_XFER, errno, + "recv_generator: mkdir %s failed", + full_fname(fname)); + skipping_dir_contents: + rprintf(FERROR, + "*** Skipping any contents from this failed directory ***\n"); + skip_dir = file; + file->flags |= FLAG_MISSING_DIR; + goto cleanup; + } + } + +#ifdef SUPPORT_XATTRS + if (preserve_xattrs && statret == 1) + copy_xattrs(fnamecmpbuf, fname); +#endif + if (set_file_attrs(fname, file, real_ret ? NULL : &real_sx, NULL, 0) + && INFO_GTE(NAME, 1) && code != FNONE && f_out != -1) + rprintf(code, "%s/\n", fname); + + /* We need to ensure that the dirs in the transfer have both + * readable and writable permissions during the time we are + * putting files within them. This is then restored to the + * former permissions after the transfer is done. */ +#ifdef HAVE_CHMOD + if (!am_root && (file->mode & S_IRWXU) != S_IRWXU && dir_tweaking) { + mode_t mode = file->mode | S_IRWXU; + if (do_chmod(fname, mode) < 0) { + rsyserr(FERROR_XFER, errno, + "failed to modify permissions on %s", + full_fname(fname)); + } + need_retouch_dir_perms = 1; + } +#endif + + if (real_ret != 0 && one_file_system) + real_sx.st.st_dev = filesystem_dev; + if (inc_recurse) { + if (one_file_system) { + uint32 *devp = F_DIR_DEV_P(file); + DEV_MAJOR(devp) = major(real_sx.st.st_dev); + DEV_MINOR(devp) = minor(real_sx.st.st_dev); + } + } + else if (delete_during && f_out != -1 && !phase + && !(file->flags & FLAG_MISSING_DIR)) { + if (file->flags & FLAG_CONTENT_DIR) + delete_in_dir(fname, file, &real_sx.st.st_dev); + else + change_local_filter_dir(fname, strlen(fname), F_DEPTH(file)); + } + goto cleanup; + } + + /* If we're not preserving permissions, change the file-list's + * mode based on the local permissions and some heuristics. */ + if (!preserve_perms) { + int exists = statret == 0 && !S_ISDIR(sx.st.st_mode); + file->mode = dest_mode(file->mode, sx.st.st_mode, dflt_perms, + exists); + } + +#ifdef SUPPORT_HARD_LINKS + if (preserve_hard_links && F_HLINK_NOT_FIRST(file) + && hard_link_check(file, ndx, fname, statret, &sx, itemizing, code)) + goto cleanup; +#endif + + if (preserve_links && S_ISLNK(file->mode)) { +#ifdef SUPPORT_LINKS + const char *sl = F_SYMLINK(file); + if (safe_symlinks && unsafe_symlink(sl, fname)) { + if (INFO_GTE(NAME, 1)) { + if (solo_file) { + /* fname contains the destination path, but we + * want to report the source path. */ + fname = f_name(file, NULL); + } + rprintf(FINFO, + "ignoring unsafe symlink \"%s\" -> \"%s\"\n", + fname, sl); + } + return; + } + if (statret == 0) { + char lnk[MAXPATHLEN]; + int len; + + if (S_ISLNK(sx.st.st_mode) + && (len = do_readlink(fname, lnk, MAXPATHLEN-1)) > 0 + && strncmp(lnk, sl, len) == 0 && sl[len] == '\0') { + /* The link is pointing to the right place. */ + set_file_attrs(fname, file, &sx, NULL, maybe_ATTRS_REPORT); + if (itemizing) + itemize(fname, file, ndx, 0, &sx, 0, 0, NULL); +#ifdef SUPPORT_HARD_LINKS + if (preserve_hard_links && F_IS_HLINKED(file)) + finish_hard_link(file, fname, ndx, &sx.st, itemizing, code, -1); +#endif + if (remove_source_files == 1) + goto return_with_success; + goto cleanup; + } + } else if (basis_dir[0] != NULL) { + int j = try_dests_non(file, fname, ndx, fnamecmpbuf, &sx, + itemizing, code); + if (j == -2) { +#ifndef CAN_HARDLINK_SYMLINK + if (link_dest) { + /* Resort to --copy-dest behavior. */ + } else +#endif + if (!copy_dest) + goto cleanup; + itemizing = 0; + code = FNONE; + } else if (j >= 0) { + statret = 1; + fnamecmp = fnamecmpbuf; + } + } + if (atomic_create(file, fname, sl, NULL, MAKEDEV(0, 0), &sx, statret == 0 ? DEL_FOR_SYMLINK : 0)) { + set_file_attrs(fname, file, NULL, NULL, 0); + if (itemizing) { + if (statret == 0 && !S_ISLNK(sx.st.st_mode)) + statret = -1; + itemize(fnamecmp, file, ndx, statret, &sx, + ITEM_LOCAL_CHANGE|ITEM_REPORT_CHANGE, 0, NULL); + } + if (code != FNONE && INFO_GTE(NAME, 1)) + rprintf(code, "%s -> %s\n", fname, sl); +#ifdef SUPPORT_HARD_LINKS + if (preserve_hard_links && F_IS_HLINKED(file)) + finish_hard_link(file, fname, ndx, NULL, itemizing, code, -1); +#endif + /* This does not check remove_source_files == 1 + * because this is one of the items that the old + * --remove-sent-files option would remove. */ + if (remove_source_files) + goto return_with_success; + } +#endif + goto cleanup; + } + + if ((am_root && preserve_devices && IS_DEVICE(file->mode)) + || (preserve_specials && IS_SPECIAL(file->mode))) { + dev_t rdev; + int del_for_flag = 0; + if (IS_DEVICE(file->mode)) { + uint32 *devp = F_RDEV_P(file); + rdev = MAKEDEV(DEV_MAJOR(devp), DEV_MINOR(devp)); + } else + rdev = 0; + if (statret == 0) { + if (IS_DEVICE(file->mode)) { + if (!IS_DEVICE(sx.st.st_mode)) + statret = -1; + del_for_flag = DEL_FOR_DEVICE; + } else { + if (!IS_SPECIAL(sx.st.st_mode)) + statret = -1; + del_for_flag = DEL_FOR_SPECIAL; + } + if (statret == 0 + && BITS_EQUAL(sx.st.st_mode, file->mode, _S_IFMT) + && (IS_SPECIAL(sx.st.st_mode) || sx.st.st_rdev == rdev)) { + /* The device or special file is identical. */ + set_file_attrs(fname, file, &sx, NULL, maybe_ATTRS_REPORT); + if (itemizing) + itemize(fname, file, ndx, 0, &sx, 0, 0, NULL); +#ifdef SUPPORT_HARD_LINKS + if (preserve_hard_links && F_IS_HLINKED(file)) + finish_hard_link(file, fname, ndx, &sx.st, itemizing, code, -1); +#endif + if (remove_source_files == 1) + goto return_with_success; + goto cleanup; + } + } else if (basis_dir[0] != NULL) { + int j = try_dests_non(file, fname, ndx, fnamecmpbuf, &sx, + itemizing, code); + if (j == -2) { +#ifndef CAN_HARDLINK_SPECIAL + if (link_dest) { + /* Resort to --copy-dest behavior. */ + } else +#endif + if (!copy_dest) + goto cleanup; + itemizing = 0; + code = FNONE; + } else if (j >= 0) { + statret = 1; + fnamecmp = fnamecmpbuf; + } + } + if (DEBUG_GTE(GENR, 1)) { + rprintf(FINFO, "mknod(%s, 0%o, [%ld,%ld])\n", + fname, (int)file->mode, + (long)major(rdev), (long)minor(rdev)); + } + if (atomic_create(file, fname, NULL, NULL, rdev, &sx, del_for_flag)) { + set_file_attrs(fname, file, NULL, NULL, 0); + if (itemizing) { + itemize(fnamecmp, file, ndx, statret, &sx, + ITEM_LOCAL_CHANGE|ITEM_REPORT_CHANGE, 0, NULL); + } + if (code != FNONE && INFO_GTE(NAME, 1)) + rprintf(code, "%s\n", fname); +#ifdef SUPPORT_HARD_LINKS + if (preserve_hard_links && F_IS_HLINKED(file)) + finish_hard_link(file, fname, ndx, NULL, itemizing, code, -1); +#endif + if (remove_source_files == 1) + goto return_with_success; + } + goto cleanup; + } + + if (!S_ISREG(file->mode)) { + if (solo_file) + fname = f_name(file, NULL); + rprintf(FINFO, "skipping non-regular file \"%s\"\n", fname); + goto cleanup; + } + + if (max_size >= 0 && F_LENGTH(file) > max_size) { + if (INFO_GTE(SKIP, 1)) { + if (solo_file) + fname = f_name(file, NULL); + rprintf(FINFO, "%s is over max-size\n", fname); + } + goto cleanup; + } + if (min_size >= 0 && F_LENGTH(file) < min_size) { + if (INFO_GTE(SKIP, 1)) { + if (solo_file) + fname = f_name(file, NULL); + rprintf(FINFO, "%s is under min-size\n", fname); + } + goto cleanup; + } + + if (update_only > 0 && statret == 0 + && cmp_time(sx.st.st_mtime, file->modtime) > 0) { + if (INFO_GTE(SKIP, 1)) + rprintf(FINFO, "%s is newer\n", fname); +#ifdef SUPPORT_HARD_LINKS + if (F_IS_HLINKED(file)) + handle_skipped_hlink(file, itemizing, code, f_out); +#endif + goto cleanup; + } + + fnamecmp_type = FNAMECMP_FNAME; + + if (statret == 0 && !S_ISREG(sx.st.st_mode)) { + if (delete_item(fname, sx.st.st_mode, del_opts | DEL_FOR_FILE) != 0) + goto cleanup; + statret = -1; + stat_errno = ENOENT; + } + + if (basis_dir[0] != NULL && (statret != 0 || !copy_dest)) { + int j = try_dests_reg(file, fname, ndx, fnamecmpbuf, &sx, + statret == 0, itemizing, code); + if (j == -2) { + if (remove_source_files == 1) + goto return_with_success; + goto cleanup; + } + if (j >= 0) { + fnamecmp = fnamecmpbuf; + fnamecmp_type = j; + statret = 0; + } + } + + init_stat_x(&real_sx); + real_sx.st = sx.st; /* Don't copy xattr/acl pointers, as they would free wrong. */ + real_ret = statret; + + if (partial_dir && (partialptr = partial_dir_fname(fname)) != NULL + && link_stat(partialptr, &partial_st, 0) == 0 + && S_ISREG(partial_st.st_mode)) { + if (statret != 0) + goto prepare_to_open; + } else + partialptr = NULL; + + if (statret != 0 && fuzzy_basis) { + /* Sets fnamecmp_type to FNAMECMP_FUZZY or above. */ + fuzzy_file = find_fuzzy(file, fuzzy_dirlist, &fnamecmp_type); + if (fuzzy_file) { + f_name(fuzzy_file, fnamecmpbuf); + if (DEBUG_GTE(FUZZY, 1)) { + rprintf(FINFO, "fuzzy basis selected for %s: %s\n", + fname, fnamecmpbuf); + } + sx.st.st_size = F_LENGTH(fuzzy_file); + statret = 0; + fnamecmp = fnamecmpbuf; + } + } + + if (statret != 0) { +#ifdef SUPPORT_HARD_LINKS + if (preserve_hard_links && F_HLINK_NOT_LAST(file)) { + cur_flist->in_progress++; + goto cleanup; + } +#endif + if (stat_errno == ENOENT) + goto notify_others; + rsyserr(FERROR_XFER, stat_errno, "recv_generator: failed to stat %s", + full_fname(fname)); + goto cleanup; + } + + if (fnamecmp_type <= FNAMECMP_BASIS_DIR_HIGH) + ; + else if (fnamecmp_type == FNAMECMP_FUZZY) + ; + else if (unchanged_file(fnamecmp, file, &sx.st)) { + if (partialptr) { + do_unlink(partialptr); + handle_partial_dir(partialptr, PDIR_DELETE); + } + set_file_attrs(fname, file, &sx, NULL, maybe_ATTRS_REPORT); + if (itemizing) + itemize(fnamecmp, file, ndx, statret, &sx, 0, 0, NULL); +#ifdef SUPPORT_HARD_LINKS + if (preserve_hard_links && F_IS_HLINKED(file)) + finish_hard_link(file, fname, ndx, &sx.st, itemizing, code, -1); +#endif + if (remove_source_files != 1) + goto cleanup; + return_with_success: + if (!dry_run) + send_msg_int(MSG_SUCCESS, ndx); + goto cleanup; + } + + if (append_mode > 0 && sx.st.st_size >= F_LENGTH(file)) { +#ifdef SUPPORT_HARD_LINKS + if (F_IS_HLINKED(file)) + handle_skipped_hlink(file, itemizing, code, f_out); +#endif + goto cleanup; + } + + prepare_to_open: + if (partialptr) { + sx.st = partial_st; + fnamecmp = partialptr; + fnamecmp_type = FNAMECMP_PARTIAL_DIR; + statret = 0; + } + + if (!do_xfers) + goto notify_others; + + if (read_batch || whole_file) { + if (inplace && make_backups > 0 && fnamecmp_type == FNAMECMP_FNAME) { + if (!(backupptr = get_backup_name(fname))) + goto cleanup; + if (!(back_file = make_file(fname, NULL, NULL, 0, NO_FILTERS))) + goto pretend_missing; + if (copy_file(fname, backupptr, -1, back_file->mode) < 0) { + unmake_file(back_file); + back_file = NULL; + goto cleanup; + } + } + goto notify_others; + } + + if (fuzzy_dirlist[0]) { + int j = flist_find(fuzzy_dirlist[0], file); + if (j >= 0) /* don't use changing file as future fuzzy basis */ + fuzzy_dirlist[0]->files[j]->flags |= FLAG_FILE_SENT; + } + + /* open the file */ + if ((fd = do_open(fnamecmp, O_RDONLY, 0)) < 0) { + rsyserr(FERROR, errno, "failed to open %s, continuing", + full_fname(fnamecmp)); + pretend_missing: + /* pretend the file didn't exist */ +#ifdef SUPPORT_HARD_LINKS + if (preserve_hard_links && F_HLINK_NOT_LAST(file)) { + cur_flist->in_progress++; + goto cleanup; + } +#endif + statret = real_ret = -1; + goto notify_others; + } + + if (inplace && make_backups > 0 && fnamecmp_type == FNAMECMP_FNAME) { + if (!(backupptr = get_backup_name(fname))) { + close(fd); + goto cleanup; + } + if (!(back_file = make_file(fname, NULL, NULL, 0, NO_FILTERS))) { + close(fd); + goto pretend_missing; + } + if (robust_unlink(backupptr) && errno != ENOENT) { + rsyserr(FERROR_XFER, errno, "unlink %s", + full_fname(backupptr)); + unmake_file(back_file); + back_file = NULL; + close(fd); + goto cleanup; + } + if ((f_copy = do_open(backupptr, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, 0600)) < 0) { + rsyserr(FERROR_XFER, errno, "open %s", full_fname(backupptr)); + unmake_file(back_file); + back_file = NULL; + close(fd); + goto cleanup; + } + fnamecmp_type = FNAMECMP_BACKUP; + } + + if (DEBUG_GTE(DELTASUM, 3)) { + rprintf(FINFO, "gen mapped %s of size %s\n", + fnamecmp, big_num(sx.st.st_size)); + } + + if (DEBUG_GTE(DELTASUM, 2)) + rprintf(FINFO, "generating and sending sums for %d\n", ndx); + + notify_others: + if (remove_source_files && !delay_updates && !phase && !dry_run) + increment_active_files(ndx, itemizing, code); + if (inc_recurse && (!dry_run || write_batch < 0)) + cur_flist->in_progress++; +#ifdef SUPPORT_HARD_LINKS + if (preserve_hard_links && F_IS_HLINKED(file)) + file->flags |= FLAG_FILE_SENT; +#endif + write_ndx(f_out, ndx); + if (itemizing) { + int iflags = ITEM_TRANSFER; + if (always_checksum > 0) + iflags |= ITEM_REPORT_CHANGE; + if (fnamecmp_type != FNAMECMP_FNAME) + iflags |= ITEM_BASIS_TYPE_FOLLOWS; + if (fnamecmp_type >= FNAMECMP_FUZZY) + iflags |= ITEM_XNAME_FOLLOWS; + itemize(fnamecmp, file, -1, real_ret, &real_sx, iflags, fnamecmp_type, + fuzzy_file ? fuzzy_file->basename : NULL); + free_stat_x(&real_sx); + } + + if (!do_xfers) { +#ifdef SUPPORT_HARD_LINKS + if (preserve_hard_links && F_IS_HLINKED(file)) + finish_hard_link(file, fname, ndx, &sx.st, itemizing, code, -1); +#endif + goto cleanup; + } + if (read_batch) + goto cleanup; + + if (statret != 0 || whole_file) + write_sum_head(f_out, NULL); + else if (sx.st.st_size <= 0) { + write_sum_head(f_out, NULL); + close(fd); + } else { + if (generate_and_send_sums(fd, sx.st.st_size, f_out, f_copy) < 0) { + rprintf(FWARNING, + "WARNING: file is too large for checksum sending: %s\n", + fnamecmp); + write_sum_head(f_out, NULL); + } + close(fd); + } + + cleanup: + if (back_file) { + int save_preserve_xattrs = preserve_xattrs; + if (f_copy >= 0) + close(f_copy); +#ifdef SUPPORT_XATTRS + if (preserve_xattrs) { + copy_xattrs(fname, backupptr); + preserve_xattrs = 0; + } +#endif + set_file_attrs(backupptr, back_file, NULL, NULL, 0); + preserve_xattrs = save_preserve_xattrs; + if (INFO_GTE(BACKUP, 1)) { + rprintf(FINFO, "backed up %s to %s\n", + fname, backupptr); + } + unmake_file(back_file); + } + + free_stat_x(&sx); +} + +/* If we are replacing an existing hard link, symlink, device, or special file, + * create a temp-name item and rename it into place. A symlimk specifies slnk, + * a hard link specifies hlnk, otherwise we create a device based on rdev. + * Specify 0 for the del_for_flag if there is not a file to replace. This + * returns 1 on success and 0 on failure. */ +int atomic_create(struct file_struct *file, char *fname, const char *slnk, const char *hlnk, + dev_t rdev, stat_x *sxp, int del_for_flag) +{ + char tmpname[MAXPATHLEN]; + const char *create_name; + int skip_atomic, dir_in_the_way = del_for_flag && S_ISDIR(sxp->st.st_mode); + + if (!del_for_flag || dir_in_the_way || tmpdir || !get_tmpname(tmpname, fname, True)) + skip_atomic = 1; + else + skip_atomic = 0; + + if (del_for_flag) { + if (make_backups > 0 && !dir_in_the_way) { + if (!make_backup(fname, skip_atomic)) + return 0; + } else if (skip_atomic) { + int del_opts = delete_mode || force_delete ? DEL_RECURSE : 0; + if (delete_item(fname, sxp->st.st_mode, del_opts | del_for_flag) != 0) + return 0; + } + } + + create_name = skip_atomic ? fname : tmpname; + + if (slnk) { +#ifdef SUPPORT_LINKS + if (do_symlink(slnk, create_name) < 0) { + rsyserr(FERROR_XFER, errno, "symlink %s -> \"%s\" failed", + full_fname(create_name), slnk); + return 0; + } +#else + return 0; +#endif + } else if (hlnk) { +#ifdef SUPPORT_HARD_LINKS + if (!hard_link_one(file, create_name, hlnk, 0)) + return 0; +#else + return 0; +#endif + } else { + if (do_mknod(create_name, file->mode, rdev) < 0) { + rsyserr(FERROR_XFER, errno, "mknod %s failed", + full_fname(create_name)); + return 0; + } + } + + if (!skip_atomic) { + if (do_rename(tmpname, fname) < 0) { + rsyserr(FERROR_XFER, errno, "rename %s -> \"%s\" failed", + full_fname(tmpname), full_fname(fname)); + do_unlink(tmpname); + return 0; + } + } + + return 1; +} + +#ifdef SUPPORT_HARD_LINKS +static void handle_skipped_hlink(struct file_struct *file, int itemizing, + enum logcode code, int f_out) +{ + char fbuf[MAXPATHLEN]; + int new_last_ndx; + struct file_list *save_flist = cur_flist; + + /* If we skip the last item in a chain of links and there was a + * prior non-skipped hard-link waiting to finish, finish it now. */ + if ((new_last_ndx = skip_hard_link(file, &cur_flist)) < 0) + return; + + file = cur_flist->files[new_last_ndx - cur_flist->ndx_start]; + cur_flist->in_progress--; /* undo prior increment */ + f_name(file, fbuf); + recv_generator(fbuf, file, new_last_ndx, itemizing, code, f_out); + + cur_flist = save_flist; +} +#endif + +static void touch_up_dirs(struct file_list *flist, int ndx) +{ + static int counter = 0; + struct file_struct *file; + char *fname; + BOOL fix_dir_perms; + int i, start, end; + + if (ndx < 0) { + start = 0; + end = flist->used - 1; + } else + start = end = ndx; + + /* Fix any directory permissions that were modified during the + * transfer and/or re-set any tweaked modified-time values. */ + for (i = start; i <= end; i++, counter++) { + file = flist->files[i]; + if (!F_IS_ACTIVE(file)) + continue; + if (!S_ISDIR(file->mode) + || (!implied_dirs && file->flags & FLAG_IMPLIED_DIR)) + continue; + if (DEBUG_GTE(TIME, 2)) { + fname = f_name(file, NULL); + rprintf(FINFO, "touch_up_dirs: %s (%d)\n", + NS(fname), i); + } + /* Be sure not to retouch permissions with --fake-super. */ + fix_dir_perms = !am_root && !(file->mode & S_IWUSR); + if (file->flags & FLAG_MISSING_DIR || !(need_retouch_dir_times || fix_dir_perms)) + continue; + fname = f_name(file, NULL); + if (fix_dir_perms) + do_chmod(fname, file->mode); + if (need_retouch_dir_times) { + STRUCT_STAT st; + if (link_stat(fname, &st, 0) == 0 + && cmp_time(st.st_mtime, file->modtime) != 0) + set_modtime(fname, file->modtime, F_MOD_NSEC(file), file->mode); + } + if (counter >= loopchk_limit) { + if (allowed_lull) + maybe_send_keepalive(time(NULL), MSK_ALLOW_FLUSH); + else + maybe_flush_socket(0); + counter = 0; + } + } +} + +void check_for_finished_files(int itemizing, enum logcode code, int check_redo) +{ + struct file_struct *file; + struct file_list *flist; + char fbuf[MAXPATHLEN]; + int ndx; + + while (1) { +#ifdef SUPPORT_HARD_LINKS + if (preserve_hard_links && (ndx = get_hlink_num()) != -1) { + int send_failed = (ndx == -2); + if (send_failed) + ndx = get_hlink_num(); + flist = flist_for_ndx(ndx, "check_for_finished_files.1"); + file = flist->files[ndx - flist->ndx_start]; + assert(file->flags & FLAG_HLINKED); + if (send_failed) + handle_skipped_hlink(file, itemizing, code, sock_f_out); + else + finish_hard_link(file, f_name(file, fbuf), ndx, NULL, itemizing, code, -1); + flist->in_progress--; + continue; + } +#endif + + if (check_redo && (ndx = get_redo_num()) != -1) { + OFF_T save_max_size = max_size; + OFF_T save_min_size = min_size; + csum_length = SUM_LENGTH; + max_size = -1; + min_size = -1; + ignore_existing = -ignore_existing; + ignore_non_existing = -ignore_non_existing; + update_only = -update_only; + always_checksum = -always_checksum; + size_only = -size_only; + append_mode = -append_mode; + make_backups = -make_backups; /* avoid dup backup w/inplace */ + ignore_times++; + + flist = cur_flist; + cur_flist = flist_for_ndx(ndx, "check_for_finished_files.2"); + + file = cur_flist->files[ndx - cur_flist->ndx_start]; + if (solo_file) + strlcpy(fbuf, solo_file, sizeof fbuf); + else + f_name(file, fbuf); + recv_generator(fbuf, file, ndx, itemizing, code, sock_f_out); + cur_flist->to_redo--; + + cur_flist = flist; + + csum_length = SHORT_SUM_LENGTH; + max_size = save_max_size; + min_size = save_min_size; + ignore_existing = -ignore_existing; + ignore_non_existing = -ignore_non_existing; + update_only = -update_only; + always_checksum = -always_checksum; + size_only = -size_only; + append_mode = -append_mode; + make_backups = -make_backups; + ignore_times--; + continue; + } + + if (cur_flist == first_flist) + break; + + /* We only get here if inc_recurse is enabled. */ + if (first_flist->in_progress || first_flist->to_redo) + break; + + write_ndx(sock_f_out, NDX_DONE); + if (!read_batch && !flist_eof) { + int old_total = 0; + for (flist = first_flist; flist != cur_flist; flist = flist->next) + old_total += flist->used; + maybe_flush_socket(!flist_eof && file_total - old_total < MIN_FILECNT_LOOKAHEAD/2); + } + + if (delete_during == 2 || !dir_tweaking) { + /* Skip directory touch-up. */ + } else if (first_flist->parent_ndx >= 0) + touch_up_dirs(dir_flist, first_flist->parent_ndx); + + flist_free(first_flist); /* updates first_flist */ + } +} + +void generate_files(int f_out, const char *local_name) +{ + int i, ndx, next_loopchk = 0; + char fbuf[MAXPATHLEN]; + int itemizing; + enum logcode code; + int save_info_flist = info_levels[INFO_FLIST]; + int save_info_progress = info_levels[INFO_PROGRESS]; + + if (protocol_version >= 29) { + itemizing = 1; + maybe_ATTRS_REPORT = stdout_format_has_i ? 0 : ATTRS_REPORT; + code = logfile_format_has_i ? FNONE : FLOG; + } else if (am_daemon) { + itemizing = logfile_format_has_i && do_xfers; + maybe_ATTRS_REPORT = ATTRS_REPORT; + code = itemizing || !do_xfers ? FCLIENT : FINFO; + } else if (!am_server) { + itemizing = stdout_format_has_i; + maybe_ATTRS_REPORT = stdout_format_has_i ? 0 : ATTRS_REPORT; + code = itemizing ? FNONE : FINFO; + } else { + itemizing = 0; + maybe_ATTRS_REPORT = ATTRS_REPORT; + code = FINFO; + } + solo_file = local_name; + dir_tweaking = !(list_only || solo_file || dry_run); + need_retouch_dir_times = preserve_times & PRESERVE_DIR_TIMES; + loopchk_limit = allowed_lull ? allowed_lull * 5 : 200; + symlink_timeset_failed_flags = ITEM_REPORT_TIME + | (protocol_version >= 30 || !am_server ? ITEM_REPORT_TIMEFAIL : 0); + implied_dirs_are_missing = relative_paths && !implied_dirs && protocol_version < 30; + + if (DEBUG_GTE(GENR, 1)) + rprintf(FINFO, "generator starting pid=%d\n", (int)getpid()); + + if (delete_before && !solo_file && cur_flist->used > 0) + do_delete_pass(); + if (delete_during == 2) { + deldelay_size = BIGPATHBUFLEN * 4; + deldelay_buf = new_array(char, deldelay_size); + if (!deldelay_buf) + out_of_memory("delete-delay"); + } + info_levels[INFO_FLIST] = info_levels[INFO_PROGRESS] = 0; + + if (append_mode > 0 || whole_file < 0) + whole_file = 0; + if (DEBUG_GTE(FLIST, 1)) { + rprintf(FINFO, "delta-transmission %s\n", + whole_file + ? "disabled for local transfer or --whole-file" + : "enabled"); + } + + dflt_perms = (ACCESSPERMS & ~orig_umask); + + do { +#ifdef SUPPORT_HARD_LINKS + if (preserve_hard_links && inc_recurse) { + while (!flist_eof && file_total < MIN_FILECNT_LOOKAHEAD/2) + wait_for_receiver(); + } +#endif + + if (inc_recurse && cur_flist->parent_ndx >= 0) { + struct file_struct *fp = dir_flist->files[cur_flist->parent_ndx]; + if (solo_file) + strlcpy(fbuf, solo_file, sizeof fbuf); + else + f_name(fp, fbuf); + ndx = cur_flist->ndx_start - 1; + recv_generator(fbuf, fp, ndx, itemizing, code, f_out); + if (delete_during && dry_run < 2 && !list_only + && !(fp->flags & FLAG_MISSING_DIR)) { + if (fp->flags & FLAG_CONTENT_DIR) { + dev_t dirdev; + if (one_file_system) { + uint32 *devp = F_DIR_DEV_P(fp); + dirdev = MAKEDEV(DEV_MAJOR(devp), DEV_MINOR(devp)); + } else + dirdev = MAKEDEV(0, 0); + delete_in_dir(fbuf, fp, &dirdev); + } else + change_local_filter_dir(fbuf, strlen(fbuf), F_DEPTH(fp)); + } + } + for (i = cur_flist->low; i <= cur_flist->high; i++) { + struct file_struct *file = cur_flist->sorted[i]; + + if (!F_IS_ACTIVE(file)) + continue; + + if (unsort_ndx) + ndx = F_NDX(file); + else + ndx = i + cur_flist->ndx_start; + + if (solo_file) + strlcpy(fbuf, solo_file, sizeof fbuf); + else + f_name(file, fbuf); + recv_generator(fbuf, file, ndx, itemizing, code, f_out); + + check_for_finished_files(itemizing, code, 0); + + if (i + cur_flist->ndx_start >= next_loopchk) { + if (allowed_lull) + maybe_send_keepalive(time(NULL), MSK_ALLOW_FLUSH); + else + maybe_flush_socket(0); + next_loopchk += loopchk_limit; + } + } + + if (!inc_recurse) { + write_ndx(f_out, NDX_DONE); + break; + } + + while (1) { + check_for_finished_files(itemizing, code, 1); + if (cur_flist->next || flist_eof) + break; + wait_for_receiver(); + } + } while ((cur_flist = cur_flist->next) != NULL); + + if (delete_during) + delete_in_dir(NULL, NULL, &dev_zero); + phase++; + if (DEBUG_GTE(GENR, 1)) + rprintf(FINFO, "generate_files phase=%d\n", phase); + + while (1) { + check_for_finished_files(itemizing, code, 1); + if (msgdone_cnt) + break; + wait_for_receiver(); + } + + phase++; + if (DEBUG_GTE(GENR, 1)) + rprintf(FINFO, "generate_files phase=%d\n", phase); + + write_ndx(f_out, NDX_DONE); + + /* Reduce round-trip lag-time for a useless delay-updates phase. */ + if (protocol_version >= 29 && EARLY_DELAY_DONE_MSG()) + write_ndx(f_out, NDX_DONE); + + if (protocol_version >= 31 && EARLY_DELETE_DONE_MSG()) { + if ((INFO_GTE(STATS, 2) && (delete_mode || force_delete)) || read_batch) + write_del_stats(f_out); + if (EARLY_DELAY_DONE_MSG()) /* Can't send this before delay */ + write_ndx(f_out, NDX_DONE); + } + + /* Read MSG_DONE for the redo phase (and any prior messages). */ + while (1) { + check_for_finished_files(itemizing, code, 0); + if (msgdone_cnt > 1) + break; + wait_for_receiver(); + } + + if (protocol_version >= 29) { + phase++; + if (DEBUG_GTE(GENR, 1)) + rprintf(FINFO, "generate_files phase=%d\n", phase); + if (!EARLY_DELAY_DONE_MSG()) { + write_ndx(f_out, NDX_DONE); + if (protocol_version >= 31 && EARLY_DELETE_DONE_MSG()) + write_ndx(f_out, NDX_DONE); + } + /* Read MSG_DONE for delay-updates phase & prior messages. */ + while (msgdone_cnt == 2) + wait_for_receiver(); + } + + info_levels[INFO_FLIST] = save_info_flist; + info_levels[INFO_PROGRESS] = save_info_progress; + + if (delete_during == 2) + do_delayed_deletions(fbuf); + if (delete_after && !solo_file && file_total > 0) + do_delete_pass(); + + if (max_delete >= 0 && skipped_deletes) { + rprintf(FWARNING, + "Deletions stopped due to --max-delete limit (%d skipped)\n", + skipped_deletes); + io_error |= IOERR_DEL_LIMIT; + } + + if (protocol_version >= 31) { + if (!EARLY_DELETE_DONE_MSG()) { + if (INFO_GTE(STATS, 2) || read_batch) + write_del_stats(f_out); + write_ndx(f_out, NDX_DONE); + } + + /* Read MSG_DONE for late-delete phase & prior messages. */ + while (msgdone_cnt == 3) + wait_for_receiver(); + } + + if ((need_retouch_dir_perms || need_retouch_dir_times) + && dir_tweaking && (!inc_recurse || delete_during == 2)) + touch_up_dirs(dir_flist, -1); + + if (DEBUG_GTE(GENR, 1)) + rprintf(FINFO, "generate_files finished\n"); +} diff --git a/rsync/getfsdev.c b/rsync/getfsdev.c new file mode 100644 index 0000000..3b113bd --- /dev/null +++ b/rsync/getfsdev.c @@ -0,0 +1,23 @@ +#include "rsync.h" + + int main(int argc, char *argv[]) +{ + STRUCT_STAT st; + int ret; + + while (--argc > 0) { +#ifdef USE_STAT64_FUNCS + ret = stat64(*++argv, &st); +#else + ret = stat(*++argv, &st); +#endif + if (ret < 0) { + fprintf(stderr, "Unable to stat `%s'\n", *argv); + exit(1); + } + printf("%ld/%ld\n", (long)major(st.st_dev), + (long)minor(st.st_dev)); + } + + return 0; +} diff --git a/rsync/getgroups.c b/rsync/getgroups.c new file mode 100644 index 0000000..bacb21b --- /dev/null +++ b/rsync/getgroups.c @@ -0,0 +1,62 @@ +/* + * Print out the gids of all groups for the current user. This is like + * `id -G` on Linux, but it's too hard to find a portable equivalent. + * + * Copyright (C) 2002 Martin Pool + * Copyright (C) 2003-2014 Wayne Davison + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * 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, visit the http://fsf.org website. + */ + +#include "rsync.h" + +int +main(UNUSED(int argc), UNUSED(char *argv[])) +{ + int n, i; + gid_t *list; + gid_t gid = MY_GID(); + int gid_in_list = 0; + +#ifdef HAVE_GETGROUPS + if ((n = getgroups(0, NULL)) < 0) { + perror("getgroups"); + return 1; + } +#else + n = 0; +#endif + + list = (gid_t*)malloc(sizeof (gid_t) * (n + 1)); + if (!list) { + fprintf(stderr, "out of memory!\n"); + exit(1); + } + +#ifdef HAVE_GETGROUPS + if (n > 0) + n = getgroups(n, list); +#endif + + for (i = 0; i < n; i++) { + printf("%lu ", (unsigned long)list[i]); + if (list[i] == gid) + gid_in_list = 1; + } + /* The default gid might not be in the list on some systems. */ + if (!gid_in_list) + printf("%lu", (unsigned long)gid); + printf("\n"); + + return 0; +} diff --git a/rsync/hashtable.c b/rsync/hashtable.c new file mode 100644 index 0000000..5cdcf61 --- /dev/null +++ b/rsync/hashtable.c @@ -0,0 +1,172 @@ +/* + * Routines to provide a memory-efficient hashtable. + * + * Copyright (C) 2007-2014 Wayne Davison + * + * 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, visit the http://fsf.org website. + */ + +#include "rsync.h" + +#define HASH_LOAD_LIMIT(size) ((size)*3/4) + +struct hashtable *hashtable_create(int size, int key64) +{ + int req = size; + struct hashtable *tbl; + int node_size = key64 ? sizeof (struct ht_int64_node) + : sizeof (struct ht_int32_node); + + /* Pick a power of 2 that can hold the requested size. */ + if (size & (size-1) || size < 16) { + size = 16; + while (size < req) + size *= 2; + } + + if (!(tbl = new(struct hashtable)) + || !(tbl->nodes = new_array0(char, size * node_size))) + out_of_memory("hashtable_create"); + tbl->size = size; + tbl->entries = 0; + tbl->node_size = node_size; + tbl->key64 = key64 ? 1 : 0; + + if (DEBUG_GTE(HASH, 1)) { + char buf[32]; + if (req != size) + snprintf(buf, sizeof buf, "req: %d, ", req); + else + *buf = '\0'; + rprintf(FINFO, "[%s] created hashtable %lx (%ssize: %d, keys: %d-bit)\n", + who_am_i(), (long)tbl, buf, size, key64 ? 64 : 32); + } + + return tbl; +} + +void hashtable_destroy(struct hashtable *tbl) +{ + if (DEBUG_GTE(HASH, 1)) { + rprintf(FINFO, "[%s] destroyed hashtable %lx (size: %d, keys: %d-bit)\n", + who_am_i(), (long)tbl, tbl->size, tbl->key64 ? 64 : 32); + } + free(tbl->nodes); + free(tbl); +} + +/* This returns the node for the indicated key, either newly created or + * already existing. Returns NULL if not allocating and not found. */ +void *hashtable_find(struct hashtable *tbl, int64 key, int allocate_if_missing) +{ + int key64 = tbl->key64; + struct ht_int32_node *node; + uint32 ndx; + + if (key64 ? key == 0 : (int32)key == 0) { + rprintf(FERROR, "Internal hashtable error: illegal key supplied!\n"); + exit_cleanup(RERR_MESSAGEIO); + } + + if (allocate_if_missing && tbl->entries > HASH_LOAD_LIMIT(tbl->size)) { + void *old_nodes = tbl->nodes; + int size = tbl->size * 2; + int i; + + if (!(tbl->nodes = new_array0(char, size * tbl->node_size))) + out_of_memory("hashtable_node"); + tbl->size = size; + tbl->entries = 0; + + if (DEBUG_GTE(HASH, 1)) { + rprintf(FINFO, "[%s] growing hashtable %lx (size: %d, keys: %d-bit)\n", + who_am_i(), (long)tbl, size, key64 ? 64 : 32); + } + + for (i = size / 2; i-- > 0; ) { + struct ht_int32_node *move_node = HT_NODE(tbl, old_nodes, i); + int64 move_key = HT_KEY(move_node, key64); + if (move_key == 0) + continue; + node = hashtable_find(tbl, move_key, 1); + node->data = move_node->data; + } + + free(old_nodes); + } + + if (!key64) { + /* Based on Jenkins One-at-a-time hash. */ + uchar buf[4], *keyp = buf; + int i; + + SIVALu(buf, 0, key); + for (ndx = 0, i = 0; i < 4; i++) { + ndx += keyp[i]; + ndx += (ndx << 10); + ndx ^= (ndx >> 6); + } + ndx += (ndx << 3); + ndx ^= (ndx >> 11); + ndx += (ndx << 15); + } else { + /* Based on Jenkins hashword() from lookup3.c. */ + uint32 a, b, c; + + /* Set up the internal state */ + a = b = c = 0xdeadbeef + (8 << 2); + +#define rot(x,k) (((x)<<(k)) ^ ((x)>>(32-(k)))) +#if SIZEOF_INT64 >= 8 + b += (uint32)(key >> 32); +#endif + a += (uint32)key; + c ^= b; c -= rot(b, 14); + a ^= c; a -= rot(c, 11); + b ^= a; b -= rot(a, 25); + c ^= b; c -= rot(b, 16); + a ^= c; a -= rot(c, 4); + b ^= a; b -= rot(a, 14); + c ^= b; c -= rot(b, 24); +#undef rot + ndx = c; + } + + /* If it already exists, return the node. If we're not + * allocating, return NULL if the key is not found. */ + while (1) { + int64 nkey; + + ndx &= tbl->size - 1; + node = HT_NODE(tbl, tbl->nodes, ndx); + nkey = HT_KEY(node, key64); + + if (nkey == key) + return node; + if (nkey == 0) { + if (!allocate_if_missing) + return NULL; + break; + } + ndx++; + } + + /* Take over this empty spot and then return the node. */ + if (key64) + ((struct ht_int64_node*)node)->key = key; + else + node->key = (int32)key; + tbl->entries++; + return node; +} diff --git a/rsync/hlink.c b/rsync/hlink.c new file mode 100644 index 0000000..3b57898 --- /dev/null +++ b/rsync/hlink.c @@ -0,0 +1,571 @@ +/* + * Routines to support hard-linking. + * + * Copyright (C) 1996 Andrew Tridgell + * Copyright (C) 1996 Paul Mackerras + * Copyright (C) 2002 Martin Pool + * Copyright (C) 2004-2014 Wayne Davison + * + * 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, visit the http://fsf.org website. + */ + +#include "rsync.h" +#include "inums.h" +#include "ifuncs.h" + +extern int dry_run; +extern int list_only; +extern int am_sender; +extern int inc_recurse; +extern int do_xfers; +extern int link_dest; +extern int preserve_acls; +extern int preserve_xattrs; +extern int protocol_version; +extern int remove_source_files; +extern int stdout_format_has_i; +extern int maybe_ATTRS_REPORT; +extern int unsort_ndx; +extern char *basis_dir[MAX_BASIS_DIRS+1]; +extern struct file_list *cur_flist; + +#ifdef SUPPORT_HARD_LINKS + +/* Starting with protocol 30, we use a simple hashtable on the sending side + * for hashing the st_dev and st_ino info. The receiving side gets told + * (via flags and a "group index") which items are hard-linked together, so + * we can avoid the pool of dev+inode data. For incremental recursion mode, + * the receiver will use a ndx hash to remember old pathnames. */ + +static struct hashtable *dev_tbl; + +static struct hashtable *prior_hlinks; + +static struct file_list *hlink_flist; + +void init_hard_links(void) +{ + if (am_sender || protocol_version < 30) + dev_tbl = hashtable_create(16, 1); + else if (inc_recurse) + prior_hlinks = hashtable_create(1024, 0); +} + +struct ht_int64_node *idev_find(int64 dev, int64 ino) +{ + static struct ht_int64_node *dev_node = NULL; + struct hashtable *tbl; + + /* Note that some OSes have a dev == 0, so increment to avoid storing a 0. */ + if (!dev_node || dev_node->key != dev+1) { + /* We keep a separate hash table of inodes for every device. */ + dev_node = hashtable_find(dev_tbl, dev+1, 1); + if (!(tbl = dev_node->data)) { + tbl = dev_node->data = hashtable_create(512, 1); + if (DEBUG_GTE(HLINK, 3)) { + rprintf(FINFO, + "[%s] created hashtable for dev %s\n", + who_am_i(), big_num(dev)); + } + } + } else + tbl = dev_node->data; + + return hashtable_find(tbl, ino, 1); +} + +void idev_destroy(void) +{ + int i; + + for (i = 0; i < dev_tbl->size; i++) { + struct ht_int32_node *node = HT_NODE(dev_tbl, dev_tbl->nodes, i); + if (node->data) + hashtable_destroy(node->data); + } + + hashtable_destroy(dev_tbl); +} + +static int hlink_compare_gnum(int *int1, int *int2) +{ + struct file_struct *f1 = hlink_flist->sorted[*int1]; + struct file_struct *f2 = hlink_flist->sorted[*int2]; + int32 gnum1 = F_HL_GNUM(f1); + int32 gnum2 = F_HL_GNUM(f2); + + if (gnum1 != gnum2) + return gnum1 > gnum2 ? 1 : -1; + + return *int1 > *int2 ? 1 : -1; +} + +static void match_gnums(int32 *ndx_list, int ndx_count) +{ + int32 from, prev; + struct file_struct *file, *file_next; + struct ht_int32_node *node = NULL; + int32 gnum, gnum_next; + + qsort(ndx_list, ndx_count, sizeof ndx_list[0], + (int (*)()) hlink_compare_gnum); + + for (from = 0; from < ndx_count; from++) { + file = hlink_flist->sorted[ndx_list[from]]; + gnum = F_HL_GNUM(file); + if (inc_recurse) { + node = hashtable_find(prior_hlinks, gnum, 1); + if (!node->data) { + if (!(node->data = new_array0(char, 5))) + out_of_memory("match_gnums"); + assert(gnum >= hlink_flist->ndx_start); + file->flags |= FLAG_HLINK_FIRST; + prev = -1; + } else if (CVAL(node->data, 0) == 0) { + struct file_list *flist; + prev = IVAL(node->data, 1); + flist = flist_for_ndx(prev, NULL); + if (flist) + flist->files[prev - flist->ndx_start]->flags &= ~FLAG_HLINK_LAST; + else { + /* We skipped all prior files in this + * group, so mark this as a "first". */ + file->flags |= FLAG_HLINK_FIRST; + prev = -1; + } + } else + prev = -1; + } else { + file->flags |= FLAG_HLINK_FIRST; + prev = -1; + } + for ( ; from < ndx_count-1; file = file_next, gnum = gnum_next, from++) { /*SHARED ITERATOR*/ + file_next = hlink_flist->sorted[ndx_list[from+1]]; + gnum_next = F_HL_GNUM(file_next); + if (gnum != gnum_next) + break; + F_HL_PREV(file) = prev; + /* The linked list uses over-the-wire ndx values. */ + if (unsort_ndx) + prev = F_NDX(file); + else + prev = ndx_list[from] + hlink_flist->ndx_start; + } + if (prev < 0 && !inc_recurse) { + /* Disable hard-link bit and set DONE so that + * HLINK_BUMP()-dependent values are unaffected. */ + file->flags &= ~(FLAG_HLINKED | FLAG_HLINK_FIRST); + file->flags |= FLAG_HLINK_DONE; + continue; + } + + file->flags |= FLAG_HLINK_LAST; + F_HL_PREV(file) = prev; + if (inc_recurse && CVAL(node->data, 0) == 0) { + if (unsort_ndx) + prev = F_NDX(file); + else + prev = ndx_list[from] + hlink_flist->ndx_start; + SIVAL(node->data, 1, prev); + } + } +} + +/* Analyze the hard-links in the file-list by creating a list of all the + * items that have hlink data, sorting them, and matching up identical + * values into clusters. These will be a single linked list from last + * to first when we're done. */ +void match_hard_links(struct file_list *flist) +{ + if (!list_only && flist->used) { + int i, ndx_count = 0; + int32 *ndx_list; + + if (!(ndx_list = new_array(int32, flist->used))) + out_of_memory("match_hard_links"); + + for (i = 0; i < flist->used; i++) { + if (F_IS_HLINKED(flist->sorted[i])) + ndx_list[ndx_count++] = i; + } + + hlink_flist = flist; + + if (ndx_count) + match_gnums(ndx_list, ndx_count); + + free(ndx_list); + } + if (protocol_version < 30) + idev_destroy(); +} + +static int maybe_hard_link(struct file_struct *file, int ndx, + char *fname, int statret, stat_x *sxp, + const char *oldname, STRUCT_STAT *old_stp, + const char *realname, int itemizing, enum logcode code) +{ + if (statret == 0) { + if (sxp->st.st_dev == old_stp->st_dev + && sxp->st.st_ino == old_stp->st_ino) { + if (itemizing) { + itemize(fname, file, ndx, statret, sxp, + ITEM_LOCAL_CHANGE | ITEM_XNAME_FOLLOWS, + 0, ""); + } + if (INFO_GTE(NAME, 2) && maybe_ATTRS_REPORT) + rprintf(FCLIENT, "%s is uptodate\n", fname); + file->flags |= FLAG_HLINK_DONE; + return 0; + } + } + + if (atomic_create(file, fname, NULL, oldname, MAKEDEV(0, 0), sxp, statret == 0 ? DEL_FOR_FILE : 0)) { + if (itemizing) { + itemize(fname, file, ndx, statret, sxp, + ITEM_LOCAL_CHANGE | ITEM_XNAME_FOLLOWS, 0, + realname); + } + if (code != FNONE && INFO_GTE(NAME, 1)) + rprintf(code, "%s => %s\n", fname, realname); + return 0; + } + + return -1; +} + +/* Figure out if a prior entry is still there or if we just have a + * cached name for it. */ +static char *check_prior(struct file_struct *file, int gnum, + int *prev_ndx_p, struct file_list **flist_p) +{ + struct file_struct *fp; + struct ht_int32_node *node; + int prev_ndx = F_HL_PREV(file); + + while (1) { + struct file_list *flist; + if (prev_ndx < 0 + || (flist = flist_for_ndx(prev_ndx, NULL)) == NULL) + break; + fp = flist->files[prev_ndx - flist->ndx_start]; + if (!(fp->flags & FLAG_SKIP_HLINK)) { + *prev_ndx_p = prev_ndx; + *flist_p = flist; + return NULL; + } + F_HL_PREV(file) = prev_ndx = F_HL_PREV(fp); + } + + if (inc_recurse + && (node = hashtable_find(prior_hlinks, gnum, 0)) != NULL) { + assert(node->data != NULL); + if (CVAL(node->data, 0) != 0) { + *prev_ndx_p = -1; + *flist_p = NULL; + return node->data; + } + /* The prior file must have been skipped. */ + F_HL_PREV(file) = -1; + } + + *prev_ndx_p = -1; + *flist_p = NULL; + return NULL; +} + +/* Only called if FLAG_HLINKED is set and FLAG_HLINK_FIRST is not. Returns: + * 0 = process the file, 1 = skip the file, -1 = error occurred. */ +int hard_link_check(struct file_struct *file, int ndx, char *fname, + int statret, stat_x *sxp, int itemizing, + enum logcode code) +{ + STRUCT_STAT prev_st; + char namebuf[MAXPATHLEN], altbuf[MAXPATHLEN]; + char *realname, *prev_name; + struct file_list *flist; + int gnum = inc_recurse ? F_HL_GNUM(file) : -1; + int prev_ndx; + + prev_name = realname = check_prior(file, gnum, &prev_ndx, &flist); + + if (!prev_name) { + struct file_struct *prev_file; + + if (!flist) { + /* The previous file was skipped, so this one is + * treated as if it were the first in its group. */ + if (DEBUG_GTE(HLINK, 2)) { + rprintf(FINFO, "hlink for %d (%s,%d): virtual first\n", + ndx, f_name(file, NULL), gnum); + } + return 0; + } + + prev_file = flist->files[prev_ndx - flist->ndx_start]; + + /* Is the previous link not complete yet? */ + if (!(prev_file->flags & FLAG_HLINK_DONE)) { + /* Is the previous link being transferred? */ + if (prev_file->flags & FLAG_FILE_SENT) { + /* Add ourselves to the list of files that will + * be updated when the transfer completes, and + * mark ourself as waiting for the transfer. */ + F_HL_PREV(file) = F_HL_PREV(prev_file); + F_HL_PREV(prev_file) = ndx; + file->flags |= FLAG_FILE_SENT; + cur_flist->in_progress++; + if (DEBUG_GTE(HLINK, 2)) { + rprintf(FINFO, "hlink for %d (%s,%d): waiting for %d\n", + ndx, f_name(file, NULL), gnum, F_HL_PREV(file)); + } + return 1; + } + if (DEBUG_GTE(HLINK, 2)) { + rprintf(FINFO, "hlink for %d (%s,%d): looking for a leader\n", + ndx, f_name(file, NULL), gnum); + } + return 0; + } + + /* There is a finished file to link with! */ + if (!(prev_file->flags & FLAG_HLINK_FIRST)) { + /* The previous previous is FIRST when prev is not. */ + prev_name = realname = check_prior(prev_file, gnum, &prev_ndx, &flist); + /* Update our previous pointer to point to the FIRST. */ + F_HL_PREV(file) = prev_ndx; + } + + if (!prev_name) { + int alt_dest; + + assert(flist != NULL); + prev_file = flist->files[prev_ndx - flist->ndx_start]; + /* F_HL_PREV() is alt_dest value when DONE && FIRST. */ + alt_dest = F_HL_PREV(prev_file); + if (DEBUG_GTE(HLINK, 2)) { + rprintf(FINFO, "hlink for %d (%s,%d): found flist match (alt %d)\n", + ndx, f_name(file, NULL), gnum, alt_dest); + } + + if (alt_dest >= 0 && dry_run) { + pathjoin(namebuf, MAXPATHLEN, basis_dir[alt_dest], + f_name(prev_file, NULL)); + prev_name = namebuf; + realname = f_name(prev_file, altbuf); + } else { + prev_name = f_name(prev_file, namebuf); + realname = prev_name; + } + } + } + + if (DEBUG_GTE(HLINK, 2)) { + rprintf(FINFO, "hlink for %d (%s,%d): leader is %d (%s)\n", + ndx, f_name(file, NULL), gnum, prev_ndx, prev_name); + } + + if (link_stat(prev_name, &prev_st, 0) < 0) { + if (!dry_run || errno != ENOENT) { + rsyserr(FERROR_XFER, errno, "stat %s failed", full_fname(prev_name)); + return -1; + } + /* A new hard-link will get a new dev & inode, so approximate + * those values in dry-run mode by zeroing them. */ + memset(&prev_st, 0, sizeof prev_st); + } + + if (statret < 0 && basis_dir[0] != NULL) { + /* If we match an alt-dest item, we don't output this as a change. */ + char cmpbuf[MAXPATHLEN]; + stat_x alt_sx; + int j = 0; + init_stat_x(&alt_sx); + do { + pathjoin(cmpbuf, MAXPATHLEN, basis_dir[j], fname); + if (link_stat(cmpbuf, &alt_sx.st, 0) < 0) + continue; + if (link_dest) { + if (prev_st.st_dev != alt_sx.st.st_dev + || prev_st.st_ino != alt_sx.st.st_ino) + continue; + statret = 1; + if (stdout_format_has_i == 0 + || (!INFO_GTE(NAME, 2) && stdout_format_has_i < 2)) { + itemizing = 0; + code = FNONE; + if (INFO_GTE(NAME, 2) && maybe_ATTRS_REPORT) + rprintf(FCLIENT, "%s is uptodate\n", fname); + } + break; + } + if (!unchanged_file(cmpbuf, file, &alt_sx.st)) + continue; + statret = 1; + if (unchanged_attrs(cmpbuf, file, &alt_sx)) + break; + } while (basis_dir[++j] != NULL); + if (statret == 1) { + sxp->st = alt_sx.st; +#ifdef SUPPORT_ACLS + if (preserve_acls && !S_ISLNK(file->mode)) { + free_acl(sxp); + if (!ACL_READY(alt_sx)) + get_acl(cmpbuf, sxp); + else { + sxp->acc_acl = alt_sx.acc_acl; + sxp->def_acl = alt_sx.def_acl; + alt_sx.acc_acl = alt_sx.def_acl = NULL; + } + } +#endif +#ifdef SUPPORT_XATTRS + if (preserve_xattrs) { + free_xattr(sxp); + if (!XATTR_READY(alt_sx)) + get_xattr(cmpbuf, sxp); + else { + sxp->xattr = alt_sx.xattr; + alt_sx.xattr = NULL; + } + } +#endif + } else + free_stat_x(&alt_sx); + } + + if (maybe_hard_link(file, ndx, fname, statret, sxp, prev_name, &prev_st, + realname, itemizing, code) < 0) + return -1; + + if (remove_source_files == 1 && do_xfers) + send_msg_int(MSG_SUCCESS, ndx); + + return 1; +} + +int hard_link_one(struct file_struct *file, const char *fname, + const char *oldname, int terse) +{ + if (do_link(oldname, fname) < 0) { + enum logcode code; + if (terse) { + if (!INFO_GTE(NAME, 1)) + return 0; + code = FINFO; + } else + code = FERROR_XFER; + rsyserr(code, errno, "link %s => %s failed", + full_fname(fname), oldname); + return 0; + } + + file->flags |= FLAG_HLINK_DONE; + + return 1; +} + +void finish_hard_link(struct file_struct *file, const char *fname, int fin_ndx, + STRUCT_STAT *stp, int itemizing, enum logcode code, + int alt_dest) +{ + stat_x prev_sx; + STRUCT_STAT st; + char prev_name[MAXPATHLEN], alt_name[MAXPATHLEN]; + const char *our_name; + struct file_list *flist; + int prev_statret, ndx, prev_ndx = F_HL_PREV(file); + + if (stp == NULL && prev_ndx >= 0) { + if (link_stat(fname, &st, 0) < 0 && !dry_run) { + rsyserr(FERROR_XFER, errno, "stat %s failed", + full_fname(fname)); + return; + } + stp = &st; + } + + /* FIRST combined with DONE means we were the first to get done. */ + file->flags |= FLAG_HLINK_FIRST | FLAG_HLINK_DONE; + F_HL_PREV(file) = alt_dest; + if (alt_dest >= 0 && dry_run) { + pathjoin(alt_name, MAXPATHLEN, basis_dir[alt_dest], + f_name(file, NULL)); + our_name = alt_name; + } else + our_name = fname; + + init_stat_x(&prev_sx); + + while ((ndx = prev_ndx) >= 0) { + int val; + flist = flist_for_ndx(ndx, "finish_hard_link"); + file = flist->files[ndx - flist->ndx_start]; + file->flags = (file->flags & ~FLAG_HLINK_FIRST) | FLAG_HLINK_DONE; + prev_ndx = F_HL_PREV(file); + F_HL_PREV(file) = fin_ndx; + prev_statret = link_stat(f_name(file, prev_name), &prev_sx.st, 0); + val = maybe_hard_link(file, ndx, prev_name, prev_statret, &prev_sx, + our_name, stp, fname, itemizing, code); + flist->in_progress--; + free_stat_x(&prev_sx); + if (val < 0) + continue; + if (remove_source_files == 1 && do_xfers) + send_msg_int(MSG_SUCCESS, ndx); + } + + if (inc_recurse) { + int gnum = F_HL_GNUM(file); + struct ht_int32_node *node = hashtable_find(prior_hlinks, gnum, 0); + if (node == NULL) { + rprintf(FERROR, "Unable to find a hlink node for %d (%s)\n", gnum, f_name(file, prev_name)); + exit_cleanup(RERR_MESSAGEIO); + } + if (node->data == NULL) { + rprintf(FERROR, "Hlink node data for %d is NULL (%s)\n", gnum, f_name(file, prev_name)); + exit_cleanup(RERR_MESSAGEIO); + } + if (CVAL(node->data, 0) != 0) { + rprintf(FERROR, "Hlink node data for %d already has path=%s (%s)\n", + gnum, (char*)node->data, f_name(file, prev_name)); + exit_cleanup(RERR_MESSAGEIO); + } + free(node->data); + if (!(node->data = strdup(our_name))) + out_of_memory("finish_hard_link"); + } +} + +int skip_hard_link(struct file_struct *file, struct file_list **flist_p) +{ + struct file_list *flist; + int prev_ndx; + + file->flags |= FLAG_SKIP_HLINK; + if (!(file->flags & FLAG_HLINK_LAST)) + return -1; + + check_prior(file, F_HL_GNUM(file), &prev_ndx, &flist); + if (prev_ndx >= 0) { + file = flist->files[prev_ndx - flist->ndx_start]; + if (file->flags & (FLAG_HLINK_DONE|FLAG_FILE_SENT)) + return -1; + file->flags |= FLAG_HLINK_LAST; + *flist_p = flist; + } + + return prev_ndx; +} +#endif diff --git a/rsync/ifuncs.h b/rsync/ifuncs.h new file mode 100644 index 0000000..6b119aa --- /dev/null +++ b/rsync/ifuncs.h @@ -0,0 +1,106 @@ +/* Inline functions for rsync. + * + * Copyright (C) 2007-2014 Wayne Davison + * + * 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, visit the http://fsf.org website. + */ + +static inline void +alloc_xbuf(xbuf *xb, size_t sz) +{ + if (!(xb->buf = new_array(char, sz))) + out_of_memory("alloc_xbuf"); + xb->size = sz; + xb->len = xb->pos = 0; +} + +static inline void +realloc_xbuf(xbuf *xb, size_t sz) +{ + char *bf = realloc_array(xb->buf, char, sz); + if (!bf) + out_of_memory("realloc_xbuf"); + xb->buf = bf; + xb->size = sz; +} + +static inline void +free_xbuf(xbuf *xb) +{ + if (xb->buf) + free(xb->buf); + memset(xb, 0, sizeof (xbuf)); +} + +static inline int +to_wire_mode(mode_t mode) +{ +#ifdef SUPPORT_LINKS +#if _S_IFLNK != 0120000 + if (S_ISLNK(mode)) + return (mode & ~(_S_IFMT)) | 0120000; +#endif +#endif + return mode; +} + +static inline mode_t +from_wire_mode(int mode) +{ +#if _S_IFLNK != 0120000 + if ((mode & (_S_IFMT)) == 0120000) + return (mode & ~(_S_IFMT)) | _S_IFLNK; +#endif + return mode; +} + +static inline char * +d_name(struct dirent *di) +{ +#ifdef HAVE_BROKEN_READDIR + return (di->d_name - 2); +#else + return di->d_name; +#endif +} + +static inline void +init_stat_x(stat_x *sx_p) +{ +#ifdef SUPPORT_ACLS + sx_p->acc_acl = sx_p->def_acl = NULL; +#endif +#ifdef SUPPORT_XATTRS + sx_p->xattr = NULL; +#endif +} + +static inline void +free_stat_x(stat_x *sx_p) +{ +#ifdef SUPPORT_ACLS + { + extern int preserve_acls; + if (preserve_acls) + free_acl(sx_p); + } +#endif +#ifdef SUPPORT_XATTRS + { + extern int preserve_xattrs; + if (preserve_xattrs) + free_xattr(sx_p); + } +#endif +} diff --git a/rsync/install-sh b/rsync/install-sh new file mode 100755 index 0000000..956817d --- /dev/null +++ b/rsync/install-sh @@ -0,0 +1,238 @@ +#! /bin/sh +# +# install - install a program, script, or datafile +# This comes from X11R5. +# +# Calling this script install-sh is preferred over install.sh, to prevent +# `make' implicit rules from creating a file called install from it +# when there is no Makefile. +# +# This script is compatible with the BSD install script, but was written +# from scratch. +# + + +# set DOITPROG to echo to test this script + +# Don't use :- since 4.3BSD and earlier shells don't like it. +doit="${DOITPROG-}" + + +# put in absolute paths if you don't have them in your path; or use env. vars. + +mvprog="${MVPROG-mv}" +cpprog="${CPPROG-cp}" +chmodprog="${CHMODPROG-chmod}" +chownprog="${CHOWNPROG-chown}" +chgrpprog="${CHGRPPROG-chgrp}" +stripprog="${STRIPPROG-strip}" +rmprog="${RMPROG-rm}" +mkdirprog="${MKDIRPROG-mkdir}" + +transformbasename="" +transform_arg="" +instcmd="$mvprog" +chmodcmd="$chmodprog 0755" +chowncmd="" +chgrpcmd="" +stripcmd="" +rmcmd="$rmprog -f" +mvcmd="$mvprog" +src="" +dst="" +dir_arg="" + +while [ x"$1" != x ]; do + case $1 in + -c) instcmd="$cpprog" + shift + continue;; + + -d) dir_arg=true + shift + continue;; + + -m) chmodcmd="$chmodprog $2" + shift + shift + continue;; + + -o) chowncmd="$chownprog $2" + shift + shift + continue;; + + -g) chgrpcmd="$chgrpprog $2" + shift + shift + continue;; + + -s) stripcmd="$stripprog" + shift + continue;; + + -t=*) transformarg=`echo $1 | sed 's/-t=//'` + shift + continue;; + + -b=*) transformbasename=`echo $1 | sed 's/-b=//'` + shift + continue;; + + *) if [ x"$src" = x ] + then + src=$1 + else + # this colon is to work around a 386BSD /bin/sh bug + : + dst=$1 + fi + shift + continue;; + esac +done + +if [ x"$src" = x ] +then + echo "install: no input file specified" + exit 1 +else + true +fi + +if [ x"$dir_arg" != x ]; then + dst=$src + src="" + + if [ -d $dst ]; then + instcmd=: + else + instcmd=mkdir + fi +else + +# Waiting for this to be detected by the "$instcmd $src $dsttmp" command +# might cause directories to be created, which would be especially bad +# if $src (and thus $dsttmp) contains '*'. + + if [ -f $src -o -d $src ] + then + true + else + echo "install: $src does not exist" + exit 1 + fi + + if [ x"$dst" = x ] + then + echo "install: no destination specified" + exit 1 + else + true + fi + +# If destination is a directory, append the input filename; if your system +# does not like double slashes in filenames, you may need to add some logic + + if [ -d $dst ] + then + dst="$dst"/`basename $src` + else + true + fi +fi + +## this sed command emulates the dirname command +dstdir=`echo $dst | sed -e 's,[^/]*$,,;s,/$,,;s,^$,.,'` + +# Make sure that the destination directory exists. +# this part is taken from Noah Friedman's mkinstalldirs script + +# Skip lots of stat calls in the usual case. +if [ ! -d "$dstdir" ]; then +defaultIFS=' +' +IFS="${IFS-${defaultIFS}}" + +oIFS="${IFS}" +# Some sh's can't handle IFS=/ for some reason. +IFS='%' +set - `echo ${dstdir} | sed -e 's@/@%@g' -e 's@^%@/@'` +IFS="${oIFS}" + +pathcomp='' + +while [ $# -ne 0 ] ; do + pathcomp="${pathcomp}${1}" + shift + + if [ ! -d "${pathcomp}" ] ; + then + $mkdirprog "${pathcomp}" + else + true + fi + + pathcomp="${pathcomp}/" +done +fi + +if [ x"$dir_arg" != x ] +then + $doit $instcmd $dst && + + if [ x"$chowncmd" != x ]; then $doit $chowncmd $dst; else true ; fi && + if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dst; else true ; fi && + if [ x"$stripcmd" != x ]; then $doit $stripcmd $dst; else true ; fi && + if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dst; else true ; fi +else + +# If we're going to rename the final executable, determine the name now. + + if [ x"$transformarg" = x ] + then + dstfile=`basename $dst` + else + dstfile=`basename $dst $transformbasename | + sed $transformarg`$transformbasename + fi + +# don't allow the sed command to completely eliminate the filename + + if [ x"$dstfile" = x ] + then + dstfile=`basename $dst` + else + true + fi + +# Make a temp file name in the proper directory. + + dsttmp=$dstdir/_inst.$$_ + +# Move or copy the file name to the temp name + + $doit $instcmd $src $dsttmp && + + trap "rm -f ${dsttmp}" 0 && + +# and set any options; do chmod last to preserve setuid bits + +# If any of these fail, we abort the whole thing. If we want to +# ignore errors from any of these, just make sure not to ignore +# errors from the above "$doit $instcmd $src $dsttmp" command. + + if [ x"$chowncmd" != x ]; then $doit $chowncmd $dsttmp; else true;fi && + if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dsttmp; else true;fi && + if [ x"$stripcmd" != x ]; then $doit $stripcmd $dsttmp; else true;fi && + if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dsttmp; else true;fi && + +# Now rename the file to the real destination. + + $doit $rmcmd -f $dstdir/$dstfile && + $doit $mvcmd $dsttmp $dstdir/$dstfile + +fi && + + +exit 0 diff --git a/rsync/inums.h b/rsync/inums.h new file mode 100644 index 0000000..3980b7a --- /dev/null +++ b/rsync/inums.h @@ -0,0 +1,57 @@ +/* Inline functions for rsync. + * + * Copyright (C) 2008-2014 Wayne Davison + * + * 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, visit the http://fsf.org website. + */ + +static inline char * +big_num(int64 num) +{ + return do_big_num(num, 0, NULL); +} + +static inline char * +comma_num(int64 num) +{ + extern int human_readable; + return do_big_num(num, human_readable != 0, NULL); +} + +static inline char * +human_num(int64 num) +{ + extern int human_readable; + return do_big_num(num, human_readable, NULL); +} + +static inline char * +big_dnum(double dnum, int decimal_digits) +{ + return do_big_dnum(dnum, 0, decimal_digits); +} + +static inline char * +comma_dnum(double dnum, int decimal_digits) +{ + extern int human_readable; + return do_big_dnum(dnum, human_readable != 0, decimal_digits); +} + +static inline char * +human_dnum(double dnum, int decimal_digits) +{ + extern int human_readable; + return do_big_dnum(dnum, human_readable, decimal_digits); +} diff --git a/rsync/io.c b/rsync/io.c new file mode 100644 index 0000000..b9a9bd0 --- /dev/null +++ b/rsync/io.c @@ -0,0 +1,2384 @@ +/* + * Socket and pipe I/O utilities used in rsync. + * + * Copyright (C) 1996-2001 Andrew Tridgell + * Copyright (C) 1996 Paul Mackerras + * Copyright (C) 2001, 2002 Martin Pool + * Copyright (C) 2003-2014 Wayne Davison + * + * 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, visit the http://fsf.org website. + */ + +/* Rsync provides its own multiplexing system, which is used to send + * stderr and stdout over a single socket. + * + * For historical reasons this is off during the start of the + * connection, but it's switched on quite early using + * io_start_multiplex_out() and io_start_multiplex_in(). */ + +#include "rsync.h" +#include "ifuncs.h" +#include "inums.h" + +/** If no timeout is specified then use a 60 second select timeout */ +#define SELECT_TIMEOUT 60 + +extern int bwlimit; +extern size_t bwlimit_writemax; +extern int io_timeout; +extern int am_server; +extern int am_sender; +extern int am_receiver; +extern int am_generator; +extern int msgs2stderr; +extern int inc_recurse; +extern int io_error; +extern int eol_nulls; +extern int flist_eof; +extern int file_total; +extern int file_old_total; +extern int list_only; +extern int read_batch; +extern int compat_flags; +extern int protect_args; +extern int checksum_seed; +extern int protocol_version; +extern int remove_source_files; +extern int preserve_hard_links; +extern BOOL extra_flist_sending_enabled; +extern BOOL flush_ok_after_signal; +extern struct stats stats; +extern struct file_list *cur_flist; +#ifdef ICONV_OPTION +extern int filesfrom_convert; +extern iconv_t ic_send, ic_recv; +#endif + +int csum_length = SHORT_SUM_LENGTH; /* initial value */ +int allowed_lull = 0; +int batch_fd = -1; +int msgdone_cnt = 0; +int forward_flist_data = 0; +BOOL flist_receiving_enabled = False; + +/* Ignore an EOF error if non-zero. See whine_about_eof(). */ +int kluge_around_eof = 0; +int got_kill_signal = -1; /* is set to 0 only after multiplexed I/O starts */ + +int sock_f_in = -1; +int sock_f_out = -1; + +int64 total_data_read = 0; +int64 total_data_written = 0; + +static struct { + xbuf in, out, msg; + int in_fd; + int out_fd; /* Both "out" and "msg" go to this fd. */ + int in_multiplexed; + unsigned out_empty_len; + size_t raw_data_header_pos; /* in the out xbuf */ + size_t raw_flushing_ends_before; /* in the out xbuf */ + size_t raw_input_ends_before; /* in the in xbuf */ +} iobuf = { .in_fd = -1, .out_fd = -1 }; + +static time_t last_io_in; +static time_t last_io_out; + +static int write_batch_monitor_in = -1; +static int write_batch_monitor_out = -1; + +static int ff_forward_fd = -1; +static int ff_reenable_multiplex = -1; +static char ff_lastchar = '\0'; +static xbuf ff_xb = EMPTY_XBUF; +#ifdef ICONV_OPTION +static xbuf iconv_buf = EMPTY_XBUF; +#endif +static int select_timeout = SELECT_TIMEOUT; +static int active_filecnt = 0; +static OFF_T active_bytecnt = 0; +static int first_message = 1; + +static char int_byte_extra[64] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* (00 - 3F)/4 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* (40 - 7F)/4 */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* (80 - BF)/4 */ + 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 5, 6, /* (C0 - FF)/4 */ +}; + +/* Our I/O buffers are sized with no bits on in the lowest byte of the "size" + * (indeed, our rounding of sizes in 1024-byte units assures more than this). + * This allows the code that is storing bytes near the physical end of a + * circular buffer to temporarily reduce the buffer's size (in order to make + * some storing idioms easier), while also making it simple to restore the + * buffer's actual size when the buffer's "pos" wraps around to the start (we + * just round the buffer's size up again). */ + +#define IOBUF_WAS_REDUCED(siz) ((siz) & 0xFF) +#define IOBUF_RESTORE_SIZE(siz) (((siz) | 0xFF) + 1) + +#define IN_MULTIPLEXED (iobuf.in_multiplexed != 0) +#define IN_MULTIPLEXED_AND_READY (iobuf.in_multiplexed > 0) +#define OUT_MULTIPLEXED (iobuf.out_empty_len != 0) + +#define PIO_NEED_INPUT (1<<0) /* The *_NEED_* flags are mutually exclusive. */ +#define PIO_NEED_OUTROOM (1<<1) +#define PIO_NEED_MSGROOM (1<<2) + +#define PIO_CONSUME_INPUT (1<<4) /* Must becombined with PIO_NEED_INPUT. */ + +#define PIO_INPUT_AND_CONSUME (PIO_NEED_INPUT | PIO_CONSUME_INPUT) +#define PIO_NEED_FLAGS (PIO_NEED_INPUT | PIO_NEED_OUTROOM | PIO_NEED_MSGROOM) + +#define REMOTE_OPTION_ERROR "rsync: on remote machine: -" +#define REMOTE_OPTION_ERROR2 ": unknown option" + +#define FILESFROM_BUFLEN 2048 + +enum festatus { FES_SUCCESS, FES_REDO, FES_NO_SEND }; + +static flist_ndx_list redo_list, hlink_list; + +static void read_a_msg(void); +static void drain_multiplex_messages(void); +static void sleep_for_bwlimit(int bytes_written); + +static void check_timeout(BOOL allow_keepalive, int keepalive_flags) +{ + time_t t, chk; + + /* On the receiving side, the generator is now the one that decides + * when a timeout has occurred. When it is sifting through a lot of + * files looking for work, it will be sending keep-alive messages to + * the sender, and even though the receiver won't be sending/receiving + * anything (not even keep-alive messages), the successful writes to + * the sender will keep things going. If the receiver is actively + * receiving data, it will ensure that the generator knows that it is + * not idle by sending the generator keep-alive messages (since the + * generator might be blocked trying to send checksums, it needs to + * know that the receiver is active). Thus, as long as one or the + * other is successfully doing work, the generator will not timeout. */ + if (!io_timeout) + return; + + t = time(NULL); + + if (allow_keepalive) { + /* This may put data into iobuf.msg w/o flushing. */ + maybe_send_keepalive(t, keepalive_flags); + } + + if (!last_io_in) + last_io_in = t; + + if (am_receiver) + return; + + chk = MAX(last_io_out, last_io_in); + if (t - chk >= io_timeout) { + if (am_server) + msgs2stderr = 1; + rprintf(FERROR, "[%s] io timeout after %d seconds -- exiting\n", + who_am_i(), (int)(t-chk)); + exit_cleanup(RERR_TIMEOUT); + } +} + +/* It's almost always an error to get an EOF when we're trying to read from the + * network, because the protocol is (for the most part) self-terminating. + * + * There is one case for the receiver when it is at the end of the transfer + * (hanging around reading any keep-alive packets that might come its way): if + * the sender dies before the generator's kill-signal comes through, we can end + * up here needing to loop until the kill-signal arrives. In this situation, + * kluge_around_eof will be < 0. + * + * There is another case for older protocol versions (< 24) where the module + * listing was not terminated, so we must ignore an EOF error in that case and + * exit. In this situation, kluge_around_eof will be > 0. */ +static NORETURN void whine_about_eof(BOOL allow_kluge) +{ + if (kluge_around_eof && allow_kluge) { + int i; + if (kluge_around_eof > 0) + exit_cleanup(0); + /* If we're still here after 10 seconds, exit with an error. */ + for (i = 10*1000/20; i--; ) + msleep(20); + } + + rprintf(FERROR, RSYNC_NAME ": connection unexpectedly closed " + "(%s bytes received so far) [%s]\n", + big_num(stats.total_read), who_am_i()); + + exit_cleanup(RERR_STREAMIO); +} + +/* Do a safe read, handling any needed looping and error handling. + * Returns the count of the bytes read, which will only be different + * from "len" if we encountered an EOF. This routine is not used on + * the socket except very early in the transfer. */ +static size_t safe_read(int fd, char *buf, size_t len) +{ + size_t got = 0; + + assert(fd != iobuf.in_fd); + + while (1) { + struct timeval tv; + fd_set r_fds, e_fds; + int cnt; + + FD_ZERO(&r_fds); + FD_SET(fd, &r_fds); + FD_ZERO(&e_fds); + FD_SET(fd, &e_fds); + tv.tv_sec = select_timeout; + tv.tv_usec = 0; + + cnt = select(fd+1, &r_fds, NULL, &e_fds, &tv); + if (cnt <= 0) { + if (cnt < 0 && errno == EBADF) { + rsyserr(FERROR, errno, "safe_read select failed [%s]", + who_am_i()); + exit_cleanup(RERR_FILEIO); + } + check_timeout(1, MSK_ALLOW_FLUSH); + continue; + } + + /*if (FD_ISSET(fd, &e_fds)) + rprintf(FINFO, "select exception on fd %d\n", fd); */ + + if (FD_ISSET(fd, &r_fds)) { + int n = read(fd, buf + got, len - got); + if (DEBUG_GTE(IO, 2)) + rprintf(FINFO, "[%s] safe_read(%d)=%ld\n", who_am_i(), fd, (long)n); + if (n == 0) + break; + if (n < 0) { + if (errno == EINTR) + continue; + rsyserr(FERROR, errno, "safe_read failed to read %ld bytes [%s]", + (long)len, who_am_i()); + exit_cleanup(RERR_STREAMIO); + } + if ((got += (size_t)n) == len) + break; + } + } + + return got; +} + +static const char *what_fd_is(int fd) +{ + static char buf[20]; + + if (fd == sock_f_out) + return "socket"; + else if (fd == iobuf.out_fd) + return "message fd"; + else if (fd == batch_fd) + return "batch file"; + else { + snprintf(buf, sizeof buf, "fd %d", fd); + return buf; + } +} + +/* Do a safe write, handling any needed looping and error handling. + * Returns only if everything was successfully written. This routine + * is not used on the socket except very early in the transfer. */ +static void safe_write(int fd, const char *buf, size_t len) +{ + int n; + + assert(fd != iobuf.out_fd); + + n = write(fd, buf, len); + if ((size_t)n == len) + return; + if (n < 0) { + if (errno != EINTR && errno != EWOULDBLOCK && errno != EAGAIN) { + write_failed: + rsyserr(FERROR, errno, + "safe_write failed to write %ld bytes to %s [%s]", + (long)len, what_fd_is(fd), who_am_i()); + exit_cleanup(RERR_STREAMIO); + } + } else { + buf += n; + len -= n; + } + + while (len) { + struct timeval tv; + fd_set w_fds; + int cnt; + + FD_ZERO(&w_fds); + FD_SET(fd, &w_fds); + tv.tv_sec = select_timeout; + tv.tv_usec = 0; + + cnt = select(fd + 1, NULL, &w_fds, NULL, &tv); + if (cnt <= 0) { + if (cnt < 0 && errno == EBADF) { + rsyserr(FERROR, errno, "safe_write select failed on %s [%s]", + what_fd_is(fd), who_am_i()); + exit_cleanup(RERR_FILEIO); + } + if (io_timeout) + maybe_send_keepalive(time(NULL), MSK_ALLOW_FLUSH); + continue; + } + + if (FD_ISSET(fd, &w_fds)) { + n = write(fd, buf, len); + if (n < 0) { + if (errno == EINTR) + continue; + goto write_failed; + } + buf += n; + len -= n; + } + } +} + +/* This is only called when files-from data is known to be available. We read + * a chunk of data and put it into the output buffer. */ +static void forward_filesfrom_data(void) +{ + int len; + + len = read(ff_forward_fd, ff_xb.buf + ff_xb.len, ff_xb.size - ff_xb.len); + if (len <= 0) { + if (len == 0 || errno != EINTR) { + /* Send end-of-file marker */ + ff_forward_fd = -1; + write_buf(iobuf.out_fd, "\0\0", ff_lastchar ? 2 : 1); + free_xbuf(&ff_xb); + if (ff_reenable_multiplex >= 0) + io_start_multiplex_out(ff_reenable_multiplex); + } + return; + } + + if (DEBUG_GTE(IO, 2)) + rprintf(FINFO, "[%s] files-from read=%ld\n", who_am_i(), (long)len); + +#ifdef ICONV_OPTION + len += ff_xb.len; +#endif + + if (!eol_nulls) { + char *s = ff_xb.buf + len; + /* Transform CR and/or LF into '\0' */ + while (s-- > ff_xb.buf) { + if (*s == '\n' || *s == '\r') + *s = '\0'; + } + } + + if (ff_lastchar) + ff_xb.pos = 0; + else { + char *s = ff_xb.buf; + /* Last buf ended with a '\0', so don't let this buf start with one. */ + while (len && *s == '\0') + s++, len--; + ff_xb.pos = s - ff_xb.buf; + } + +#ifdef ICONV_OPTION + if (filesfrom_convert && len) { + char *sob = ff_xb.buf + ff_xb.pos, *s = sob; + char *eob = sob + len; + int flags = ICB_INCLUDE_BAD | ICB_INCLUDE_INCOMPLETE | ICB_CIRCULAR_OUT; + if (ff_lastchar == '\0') + flags |= ICB_INIT; + /* Convert/send each null-terminated string separately, skipping empties. */ + while (s != eob) { + if (*s++ == '\0') { + ff_xb.len = s - sob - 1; + if (iconvbufs(ic_send, &ff_xb, &iobuf.out, flags) < 0) + exit_cleanup(RERR_PROTOCOL); /* impossible? */ + write_buf(iobuf.out_fd, s-1, 1); /* Send the '\0'. */ + while (s != eob && *s == '\0') + s++; + sob = s; + ff_xb.pos = sob - ff_xb.buf; + flags |= ICB_INIT; + } + } + + if ((ff_xb.len = s - sob) == 0) + ff_lastchar = '\0'; + else { + /* Handle a partial string specially, saving any incomplete chars. */ + flags &= ~ICB_INCLUDE_INCOMPLETE; + if (iconvbufs(ic_send, &ff_xb, &iobuf.out, flags) < 0) { + if (errno == E2BIG) + exit_cleanup(RERR_PROTOCOL); /* impossible? */ + if (ff_xb.pos) + memmove(ff_xb.buf, ff_xb.buf + ff_xb.pos, ff_xb.len); + } + ff_lastchar = 'x'; /* Anything non-zero. */ + } + } else +#endif + + if (len) { + char *f = ff_xb.buf + ff_xb.pos; + char *t = ff_xb.buf; + char *eob = f + len; + /* Eliminate any multi-'\0' runs. */ + while (f != eob) { + if (!(*t++ = *f++)) { + while (f != eob && *f == '\0') + f++; + } + } + ff_lastchar = f[-1]; + if ((len = t - ff_xb.buf) != 0) { + /* This will not circle back to perform_io() because we only get + * called when there is plenty of room in the output buffer. */ + write_buf(iobuf.out_fd, ff_xb.buf, len); + } + } +} + +void reduce_iobuf_size(xbuf *out, size_t new_size) +{ + if (new_size < out->size) { + /* Avoid weird buffer interactions by only outputting this to stderr. */ + if (msgs2stderr && DEBUG_GTE(IO, 4)) { + const char *name = out == &iobuf.out ? "iobuf.out" + : out == &iobuf.msg ? "iobuf.msg" + : NULL; + if (name) { + rprintf(FINFO, "[%s] reduced size of %s (-%d)\n", + who_am_i(), name, (int)(out->size - new_size)); + } + } + out->size = new_size; + } +} + +void restore_iobuf_size(xbuf *out) +{ + if (IOBUF_WAS_REDUCED(out->size)) { + size_t new_size = IOBUF_RESTORE_SIZE(out->size); + /* Avoid weird buffer interactions by only outputting this to stderr. */ + if (msgs2stderr && DEBUG_GTE(IO, 4)) { + const char *name = out == &iobuf.out ? "iobuf.out" + : out == &iobuf.msg ? "iobuf.msg" + : NULL; + if (name) { + rprintf(FINFO, "[%s] restored size of %s (+%d)\n", + who_am_i(), name, (int)(new_size - out->size)); + } + } + out->size = new_size; + } +} + +static void handle_kill_signal(BOOL flush_ok) +{ + got_kill_signal = -1; + flush_ok_after_signal = flush_ok; + exit_cleanup(RERR_SIGNAL); +} + +/* Perform buffered input and/or output until specified conditions are met. + * When given a "needed" read or write request, this returns without doing any + * I/O if the needed input bytes or write space is already available. Once I/O + * is needed, this will try to do whatever reading and/or writing is currently + * possible, up to the maximum buffer allowances, no matter if this is a read + * or write request. However, the I/O stops as soon as the required input + * bytes or output space is available. If this is not a read request, the + * routine may also do some advantageous reading of messages from a multiplexed + * input source (which ensures that we don't jam up with everyone in their + * "need to write" code and nobody reading the accumulated data that would make + * writing possible). + * + * The iobuf.in, .out and .msg buffers are all circular. Callers need to be + * aware that some data copies will need to be split when the bytes wrap around + * from the end to the start. In order to help make writing into the output + * buffers easier for some operations (such as the use of SIVAL() into the + * buffer) a buffer may be temporarily shortened by a small amount, but the + * original size will be automatically restored when the .pos wraps to the + * start. See also the 3 raw_* iobuf vars that are used in the handling of + * MSG_DATA bytes as they are read-from/written-into the buffers. + * + * When writing, we flush data in the following priority order: + * + * 1. Finish writing any in-progress MSG_DATA sequence from iobuf.out. + * + * 2. Write out all the messages from the message buf (if iobuf.msg is active). + * Yes, this means that a PIO_NEED_OUTROOM call will completely flush any + * messages before getting to the iobuf.out flushing (except for rule 1). + * + * 3. Write out the raw data from iobuf.out, possibly filling in the multiplexed + * MSG_DATA header that was pre-allocated (when output is multiplexed). + * + * TODO: items for possible future work: + * + * - Make this routine able to read the generator-to-receiver batch flow? + * + * Unlike the old routines that this replaces, it is OK to read ahead as far as + * we can because the read_a_msg() routine now reads its bytes out of the input + * buffer. In the old days, only raw data was in the input buffer, and any + * unused raw data in the buf would prevent the reading of socket data. */ +static char *perform_io(size_t needed, int flags) +{ + fd_set r_fds, e_fds, w_fds; + struct timeval tv; + int cnt, max_fd; + size_t empty_buf_len = 0; + xbuf *out; + char *data; + + if (iobuf.in.len == 0 && iobuf.in.pos != 0) { + if (iobuf.raw_input_ends_before) + iobuf.raw_input_ends_before -= iobuf.in.pos; + iobuf.in.pos = 0; + } + + switch (flags & PIO_NEED_FLAGS) { + case PIO_NEED_INPUT: + /* We never resize the circular input buffer. */ + if (iobuf.in.size < needed) { + rprintf(FERROR, "need to read %ld bytes, iobuf.in.buf is only %ld bytes.\n", + (long)needed, (long)iobuf.in.size); + exit_cleanup(RERR_PROTOCOL); + } + + if (msgs2stderr && DEBUG_GTE(IO, 3)) { + rprintf(FINFO, "[%s] perform_io(%ld, %sinput)\n", + who_am_i(), (long)needed, flags & PIO_CONSUME_INPUT ? "consume&" : ""); + } + break; + + case PIO_NEED_OUTROOM: + /* We never resize the circular output buffer. */ + if (iobuf.out.size - iobuf.out_empty_len < needed) { + fprintf(stderr, "need to write %ld bytes, iobuf.out.buf is only %ld bytes.\n", + (long)needed, (long)(iobuf.out.size - iobuf.out_empty_len)); + exit_cleanup(RERR_PROTOCOL); + } + + if (msgs2stderr && DEBUG_GTE(IO, 3)) { + rprintf(FINFO, "[%s] perform_io(%ld, outroom) needs to flush %ld\n", + who_am_i(), (long)needed, + iobuf.out.len + needed > iobuf.out.size + ? (long)(iobuf.out.len + needed - iobuf.out.size) : 0L); + } + break; + + case PIO_NEED_MSGROOM: + /* We never resize the circular message buffer. */ + if (iobuf.msg.size < needed) { + fprintf(stderr, "need to write %ld bytes, iobuf.msg.buf is only %ld bytes.\n", + (long)needed, (long)iobuf.msg.size); + exit_cleanup(RERR_PROTOCOL); + } + + if (msgs2stderr && DEBUG_GTE(IO, 3)) { + rprintf(FINFO, "[%s] perform_io(%ld, msgroom) needs to flush %ld\n", + who_am_i(), (long)needed, + iobuf.msg.len + needed > iobuf.msg.size + ? (long)(iobuf.msg.len + needed - iobuf.msg.size) : 0L); + } + break; + + case 0: + if (msgs2stderr && DEBUG_GTE(IO, 3)) + rprintf(FINFO, "[%s] perform_io(%ld, %d)\n", who_am_i(), (long)needed, flags); + break; + + default: + exit_cleanup(RERR_UNSUPPORTED); + } + + while (1) { + switch (flags & PIO_NEED_FLAGS) { + case PIO_NEED_INPUT: + if (iobuf.in.len >= needed) + goto double_break; + break; + case PIO_NEED_OUTROOM: + /* Note that iobuf.out_empty_len doesn't factor into this check + * because iobuf.out.len already holds any needed header len. */ + if (iobuf.out.len + needed <= iobuf.out.size) + goto double_break; + break; + case PIO_NEED_MSGROOM: + if (iobuf.msg.len + needed <= iobuf.msg.size) + goto double_break; + break; + } + + max_fd = -1; + + FD_ZERO(&r_fds); + FD_ZERO(&e_fds); + if (iobuf.in_fd >= 0 && iobuf.in.size - iobuf.in.len) { + if (!read_batch || batch_fd >= 0) { + FD_SET(iobuf.in_fd, &r_fds); + FD_SET(iobuf.in_fd, &e_fds); + } + if (iobuf.in_fd > max_fd) + max_fd = iobuf.in_fd; + } + + /* Only do more filesfrom processing if there is enough room in the out buffer. */ + if (ff_forward_fd >= 0 && iobuf.out.size - iobuf.out.len > FILESFROM_BUFLEN*2) { + FD_SET(ff_forward_fd, &r_fds); + if (ff_forward_fd > max_fd) + max_fd = ff_forward_fd; + } + + FD_ZERO(&w_fds); + if (iobuf.out_fd >= 0) { + if (iobuf.raw_flushing_ends_before + || (!iobuf.msg.len && iobuf.out.len > iobuf.out_empty_len && !(flags & PIO_NEED_MSGROOM))) { + if (OUT_MULTIPLEXED && !iobuf.raw_flushing_ends_before) { + /* The iobuf.raw_flushing_ends_before value can point off the end + * of the iobuf.out buffer for a while, for easier subtracting. */ + iobuf.raw_flushing_ends_before = iobuf.out.pos + iobuf.out.len; + + SIVAL(iobuf.out.buf + iobuf.raw_data_header_pos, 0, + ((MPLEX_BASE + (int)MSG_DATA)<<24) + iobuf.out.len - 4); + + if (msgs2stderr && DEBUG_GTE(IO, 1)) { + rprintf(FINFO, "[%s] send_msg(%d, %ld)\n", + who_am_i(), (int)MSG_DATA, (long)iobuf.out.len - 4); + } + + /* reserve room for the next MSG_DATA header */ + iobuf.raw_data_header_pos = iobuf.raw_flushing_ends_before; + if (iobuf.raw_data_header_pos >= iobuf.out.size) + iobuf.raw_data_header_pos -= iobuf.out.size; + else if (iobuf.raw_data_header_pos + 4 > iobuf.out.size) { + /* The 4-byte header won't fit at the end of the buffer, + * so we'll temporarily reduce the output buffer's size + * and put the header at the start of the buffer. */ + reduce_iobuf_size(&iobuf.out, iobuf.raw_data_header_pos); + iobuf.raw_data_header_pos = 0; + } + /* Yes, it is possible for this to make len > size for a while. */ + iobuf.out.len += 4; + } + + empty_buf_len = iobuf.out_empty_len; + out = &iobuf.out; + } else if (iobuf.msg.len) { + empty_buf_len = 0; + out = &iobuf.msg; + } else + out = NULL; + if (out) { + FD_SET(iobuf.out_fd, &w_fds); + if (iobuf.out_fd > max_fd) + max_fd = iobuf.out_fd; + } + } else + out = NULL; + + if (max_fd < 0) { + switch (flags & PIO_NEED_FLAGS) { + case PIO_NEED_INPUT: + iobuf.in.len = 0; + if (kluge_around_eof == 2) + exit_cleanup(0); + if (iobuf.in_fd == -2) + whine_about_eof(True); + rprintf(FERROR, "error in perform_io: no fd for input.\n"); + exit_cleanup(RERR_PROTOCOL); + case PIO_NEED_OUTROOM: + case PIO_NEED_MSGROOM: + msgs2stderr = 1; + drain_multiplex_messages(); + if (iobuf.out_fd == -2) + whine_about_eof(True); + rprintf(FERROR, "error in perform_io: no fd for output.\n"); + exit_cleanup(RERR_PROTOCOL); + default: + /* No stated needs, so I guess this is OK. */ + break; + } + break; + } + + if (got_kill_signal > 0) + handle_kill_signal(True); + + if (extra_flist_sending_enabled) { + if (file_total - file_old_total < MAX_FILECNT_LOOKAHEAD && IN_MULTIPLEXED_AND_READY) + tv.tv_sec = 0; + else { + extra_flist_sending_enabled = False; + tv.tv_sec = select_timeout; + } + } else + tv.tv_sec = select_timeout; + tv.tv_usec = 0; + + cnt = select(max_fd + 1, &r_fds, &w_fds, &e_fds, &tv); + + if (cnt <= 0) { + if (cnt < 0 && errno == EBADF) { + msgs2stderr = 1; + exit_cleanup(RERR_SOCKETIO); + } + if (extra_flist_sending_enabled) { + extra_flist_sending_enabled = False; + send_extra_file_list(sock_f_out, -1); + extra_flist_sending_enabled = !flist_eof; + } else + check_timeout((flags & PIO_NEED_INPUT) != 0, 0); + FD_ZERO(&r_fds); /* Just in case... */ + FD_ZERO(&w_fds); + } + + if (iobuf.in_fd >= 0 && FD_ISSET(iobuf.in_fd, &r_fds)) { + size_t len, pos = iobuf.in.pos + iobuf.in.len; + int n; + if (pos >= iobuf.in.size) { + pos -= iobuf.in.size; + len = iobuf.in.size - iobuf.in.len; + } else + len = iobuf.in.size - pos; + if ((n = read(iobuf.in_fd, iobuf.in.buf + pos, len)) <= 0) { + if (n == 0) { + /* Signal that input has become invalid. */ + if (!read_batch || batch_fd < 0 || am_generator) + iobuf.in_fd = -2; + batch_fd = -1; + continue; + } + if (errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN) + n = 0; + else { + /* Don't write errors on a dead socket. */ + if (iobuf.in_fd == sock_f_in) { + if (am_sender) + msgs2stderr = 1; + rsyserr(FERROR_SOCKET, errno, "read error"); + } else + rsyserr(FERROR, errno, "read error"); + exit_cleanup(RERR_SOCKETIO); + } + } + if (msgs2stderr && DEBUG_GTE(IO, 2)) + rprintf(FINFO, "[%s] recv=%ld\n", who_am_i(), (long)n); + + if (io_timeout) { + last_io_in = time(NULL); + if (flags & PIO_NEED_INPUT) + maybe_send_keepalive(last_io_in, 0); + } + stats.total_read += n; + + iobuf.in.len += n; + } + + if (out && FD_ISSET(iobuf.out_fd, &w_fds)) { + size_t len = iobuf.raw_flushing_ends_before ? iobuf.raw_flushing_ends_before - out->pos : out->len; + int n; + + if (bwlimit_writemax && len > bwlimit_writemax) + len = bwlimit_writemax; + + if (out->pos + len > out->size) + len = out->size - out->pos; + if ((n = write(iobuf.out_fd, out->buf + out->pos, len)) <= 0) { + if (errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN) + n = 0; + else { + /* Don't write errors on a dead socket. */ + msgs2stderr = 1; + iobuf.out_fd = -2; + iobuf.out.len = iobuf.msg.len = iobuf.raw_flushing_ends_before = 0; + rsyserr(FERROR_SOCKET, errno, "[%s] write error", who_am_i()); + drain_multiplex_messages(); + exit_cleanup(RERR_SOCKETIO); + } + } + if (msgs2stderr && DEBUG_GTE(IO, 2)) { + rprintf(FINFO, "[%s] %s sent=%ld\n", + who_am_i(), out == &iobuf.out ? "out" : "msg", (long)n); + } + + if (io_timeout) + last_io_out = time(NULL); + stats.total_written += n; + + if (bwlimit_writemax) + sleep_for_bwlimit(n); + + if ((out->pos += n) == out->size) { + if (iobuf.raw_flushing_ends_before) + iobuf.raw_flushing_ends_before -= out->size; + out->pos = 0; + restore_iobuf_size(out); + } else if (out->pos == iobuf.raw_flushing_ends_before) + iobuf.raw_flushing_ends_before = 0; + if ((out->len -= n) == empty_buf_len) { + out->pos = 0; + restore_iobuf_size(out); + if (empty_buf_len) + iobuf.raw_data_header_pos = 0; + } + } + + if (got_kill_signal > 0) + handle_kill_signal(True); + + /* We need to help prevent deadlock by doing what reading + * we can whenever we are here trying to write. */ + if (IN_MULTIPLEXED_AND_READY && !(flags & PIO_NEED_INPUT)) { + while (!iobuf.raw_input_ends_before && iobuf.in.len > 512) + read_a_msg(); + if (flist_receiving_enabled && iobuf.in.len > 512) + wait_for_receiver(); /* generator only */ + } + + if (ff_forward_fd >= 0 && FD_ISSET(ff_forward_fd, &r_fds)) { + /* This can potentially flush all output and enable + * multiplexed output, so keep this last in the loop + * and be sure to not cache anything that would break + * such a change. */ + forward_filesfrom_data(); + } + } + double_break: + + if (got_kill_signal > 0) + handle_kill_signal(True); + + data = iobuf.in.buf + iobuf.in.pos; + + if (flags & PIO_CONSUME_INPUT) { + iobuf.in.len -= needed; + iobuf.in.pos += needed; + if (iobuf.in.pos == iobuf.raw_input_ends_before) + iobuf.raw_input_ends_before = 0; + if (iobuf.in.pos >= iobuf.in.size) { + iobuf.in.pos -= iobuf.in.size; + if (iobuf.raw_input_ends_before) + iobuf.raw_input_ends_before -= iobuf.in.size; + } + } + + return data; +} + +static void raw_read_buf(char *buf, size_t len) +{ + size_t pos = iobuf.in.pos; + char *data = perform_io(len, PIO_INPUT_AND_CONSUME); + if (iobuf.in.pos <= pos && len) { + size_t siz = len - iobuf.in.pos; + memcpy(buf, data, siz); + memcpy(buf + siz, iobuf.in.buf, iobuf.in.pos); + } else + memcpy(buf, data, len); +} + +static int32 raw_read_int(void) +{ + char *data, buf[4]; + if (iobuf.in.size - iobuf.in.pos >= 4) + data = perform_io(4, PIO_INPUT_AND_CONSUME); + else + raw_read_buf(data = buf, 4); + return IVAL(data, 0); +} + +void noop_io_until_death(void) +{ + char buf[1024]; + + if (!iobuf.in.buf || !iobuf.out.buf || iobuf.in_fd < 0 || iobuf.out_fd < 0 || kluge_around_eof) + return; + + kluge_around_eof = 2; + /* Setting an I/O timeout ensures that if something inexplicably weird + * happens, we won't hang around forever. */ + if (!io_timeout) + set_io_timeout(60); + + while (1) + read_buf(iobuf.in_fd, buf, sizeof buf); +} + +/* Buffer a message for the multiplexed output stream. Is not used for (normal) MSG_DATA. */ +int send_msg(enum msgcode code, const char *buf, size_t len, int convert) +{ + char *hdr; + size_t needed, pos; + BOOL want_debug = DEBUG_GTE(IO, 1) && convert >= 0 && (msgs2stderr || code != MSG_INFO); + + if (!OUT_MULTIPLEXED) + return 0; + + if (want_debug) + rprintf(FINFO, "[%s] send_msg(%d, %ld)\n", who_am_i(), (int)code, (long)len); + + /* When checking for enough free space for this message, we need to + * make sure that there is space for the 4-byte header, plus we'll + * assume that we may waste up to 3 bytes (if the header doesn't fit + * at the physical end of the buffer). */ +#ifdef ICONV_OPTION + if (convert > 0 && ic_send == (iconv_t)-1) + convert = 0; + if (convert > 0) { + /* Ensuring double-size room leaves space for maximal conversion expansion. */ + needed = len*2 + 4 + 3; + } else +#endif + needed = len + 4 + 3; + if (iobuf.msg.len + needed > iobuf.msg.size) + perform_io(needed, PIO_NEED_MSGROOM); + + pos = iobuf.msg.pos + iobuf.msg.len; /* Must be set after any flushing. */ + if (pos >= iobuf.msg.size) + pos -= iobuf.msg.size; + else if (pos + 4 > iobuf.msg.size) { + /* The 4-byte header won't fit at the end of the buffer, + * so we'll temporarily reduce the message buffer's size + * and put the header at the start of the buffer. */ + reduce_iobuf_size(&iobuf.msg, pos); + pos = 0; + } + hdr = iobuf.msg.buf + pos; + + iobuf.msg.len += 4; /* Allocate room for the coming header bytes. */ + +#ifdef ICONV_OPTION + if (convert > 0) { + xbuf inbuf; + + INIT_XBUF(inbuf, (char*)buf, len, (size_t)-1); + + len = iobuf.msg.len; + iconvbufs(ic_send, &inbuf, &iobuf.msg, + ICB_INCLUDE_BAD | ICB_INCLUDE_INCOMPLETE | ICB_CIRCULAR_OUT | ICB_INIT); + if (inbuf.len > 0) { + rprintf(FERROR, "overflowed iobuf.msg buffer in send_msg"); + exit_cleanup(RERR_UNSUPPORTED); + } + len = iobuf.msg.len - len; + } else +#endif + { + size_t siz; + + if ((pos += 4) == iobuf.msg.size) + pos = 0; + + /* Handle a split copy if we wrap around the end of the circular buffer. */ + if (pos >= iobuf.msg.pos && (siz = iobuf.msg.size - pos) < len) { + memcpy(iobuf.msg.buf + pos, buf, siz); + memcpy(iobuf.msg.buf, buf + siz, len - siz); + } else + memcpy(iobuf.msg.buf + pos, buf, len); + + iobuf.msg.len += len; + } + + SIVAL(hdr, 0, ((MPLEX_BASE + (int)code)<<24) + len); + + if (want_debug && convert > 0) + rprintf(FINFO, "[%s] converted msg len=%ld\n", who_am_i(), (long)len); + + return 1; +} + +void send_msg_int(enum msgcode code, int num) +{ + char numbuf[4]; + + if (DEBUG_GTE(IO, 1)) + rprintf(FINFO, "[%s] send_msg_int(%d, %d)\n", who_am_i(), (int)code, num); + + SIVAL(numbuf, 0, num); + send_msg(code, numbuf, 4, -1); +} + +static void got_flist_entry_status(enum festatus status, int ndx) +{ + struct file_list *flist = flist_for_ndx(ndx, "got_flist_entry_status"); + + if (remove_source_files) { + active_filecnt--; + active_bytecnt -= F_LENGTH(flist->files[ndx - flist->ndx_start]); + } + + if (inc_recurse) + flist->in_progress--; + + switch (status) { + case FES_SUCCESS: + if (remove_source_files) + send_msg_int(MSG_SUCCESS, ndx); + /* FALL THROUGH */ + case FES_NO_SEND: +#ifdef SUPPORT_HARD_LINKS + if (preserve_hard_links) { + struct file_struct *file = flist->files[ndx - flist->ndx_start]; + if (F_IS_HLINKED(file)) { + if (status == FES_NO_SEND) + flist_ndx_push(&hlink_list, -2); /* indicates a failure follows */ + flist_ndx_push(&hlink_list, ndx); + if (inc_recurse) + flist->in_progress++; + } + } +#endif + break; + case FES_REDO: + if (read_batch) { + if (inc_recurse) + flist->in_progress++; + break; + } + if (inc_recurse) + flist->to_redo++; + flist_ndx_push(&redo_list, ndx); + break; + } +} + +/* Note the fds used for the main socket (which might really be a pipe + * for a local transfer, but we can ignore that). */ +void io_set_sock_fds(int f_in, int f_out) +{ + sock_f_in = f_in; + sock_f_out = f_out; +} + +void set_io_timeout(int secs) +{ + io_timeout = secs; + allowed_lull = (io_timeout + 1) / 2; + + if (!io_timeout || allowed_lull > SELECT_TIMEOUT) + select_timeout = SELECT_TIMEOUT; + else + select_timeout = allowed_lull; + + if (read_batch) + allowed_lull = 0; +} + +static void check_for_d_option_error(const char *msg) +{ + static char rsync263_opts[] = "BCDHIKLPRSTWabceghlnopqrtuvxz"; + char *colon; + int saw_d = 0; + + if (*msg != 'r' + || strncmp(msg, REMOTE_OPTION_ERROR, sizeof REMOTE_OPTION_ERROR - 1) != 0) + return; + + msg += sizeof REMOTE_OPTION_ERROR - 1; + if (*msg == '-' || (colon = strchr(msg, ':')) == NULL + || strncmp(colon, REMOTE_OPTION_ERROR2, sizeof REMOTE_OPTION_ERROR2 - 1) != 0) + return; + + for ( ; *msg != ':'; msg++) { + if (*msg == 'd') + saw_d = 1; + else if (*msg == 'e') + break; + else if (strchr(rsync263_opts, *msg) == NULL) + return; + } + + if (saw_d) { + rprintf(FWARNING, + "*** Try using \"--old-d\" if remote rsync is <= 2.6.3 ***\n"); + } +} + +/* This is used by the generator to limit how many file transfers can + * be active at once when --remove-source-files is specified. Without + * this, sender-side deletions were mostly happening at the end. */ +void increment_active_files(int ndx, int itemizing, enum logcode code) +{ + while (1) { + /* TODO: tune these limits? */ + int limit = active_bytecnt >= 128*1024 ? 10 : 50; + if (active_filecnt < limit) + break; + check_for_finished_files(itemizing, code, 0); + if (active_filecnt < limit) + break; + wait_for_receiver(); + } + + active_filecnt++; + active_bytecnt += F_LENGTH(cur_flist->files[ndx - cur_flist->ndx_start]); +} + +int get_redo_num(void) +{ + return flist_ndx_pop(&redo_list); +} + +int get_hlink_num(void) +{ + return flist_ndx_pop(&hlink_list); +} + +/* When we're the receiver and we have a local --files-from list of names + * that needs to be sent over the socket to the sender, we have to do two + * things at the same time: send the sender a list of what files we're + * processing and read the incoming file+info list from the sender. We do + * this by making recv_file_list() call forward_filesfrom_data(), which + * will ensure that we forward data to the sender until we get some data + * for recv_file_list() to use. */ +void start_filesfrom_forwarding(int fd) +{ + if (protocol_version < 31 && OUT_MULTIPLEXED) { + /* Older protocols send the files-from data w/o packaging + * it in multiplexed I/O packets, so temporarily switch + * to buffered I/O to match this behavior. */ + iobuf.msg.pos = iobuf.msg.len = 0; /* Be extra sure no messages go out. */ + ff_reenable_multiplex = io_end_multiplex_out(MPLX_TO_BUFFERED); + } + ff_forward_fd = fd; + + alloc_xbuf(&ff_xb, FILESFROM_BUFLEN); +} + +/* Read a line into the "buf" buffer. */ +int read_line(int fd, char *buf, size_t bufsiz, int flags) +{ + char ch, *s, *eob; + +#ifdef ICONV_OPTION + if (flags & RL_CONVERT && iconv_buf.size < bufsiz) + realloc_xbuf(&iconv_buf, bufsiz + 1024); +#endif + + start: +#ifdef ICONV_OPTION + s = flags & RL_CONVERT ? iconv_buf.buf : buf; +#else + s = buf; +#endif + eob = s + bufsiz - 1; + while (1) { + /* We avoid read_byte() for files because files can return an EOF. */ + if (fd == iobuf.in_fd) + ch = read_byte(fd); + else if (safe_read(fd, &ch, 1) == 0) + break; + if (flags & RL_EOL_NULLS ? ch == '\0' : (ch == '\r' || ch == '\n')) { + /* Skip empty lines if dumping comments. */ + if (flags & RL_DUMP_COMMENTS && s == buf) + continue; + break; + } + if (s < eob) + *s++ = ch; + } + *s = '\0'; + + if (flags & RL_DUMP_COMMENTS && (*buf == '#' || *buf == ';')) + goto start; + +#ifdef ICONV_OPTION + if (flags & RL_CONVERT) { + xbuf outbuf; + INIT_XBUF(outbuf, buf, 0, bufsiz); + iconv_buf.pos = 0; + iconv_buf.len = s - iconv_buf.buf; + iconvbufs(ic_recv, &iconv_buf, &outbuf, + ICB_INCLUDE_BAD | ICB_INCLUDE_INCOMPLETE | ICB_INIT); + outbuf.buf[outbuf.len] = '\0'; + return outbuf.len; + } +#endif + + return s - buf; +} + +void read_args(int f_in, char *mod_name, char *buf, size_t bufsiz, int rl_nulls, + char ***argv_p, int *argc_p, char **request_p) +{ + int maxargs = MAX_ARGS; + int dot_pos = 0, argc = 0, request_len = 0; + char **argv, *p; + int rl_flags = (rl_nulls ? RL_EOL_NULLS : 0); + +#ifdef ICONV_OPTION + rl_flags |= (protect_args && ic_recv != (iconv_t)-1 ? RL_CONVERT : 0); +#endif + + if (!(argv = new_array(char *, maxargs))) + out_of_memory("read_args"); + if (mod_name && !protect_args) + argv[argc++] = "rsyncd"; + + if (request_p) + *request_p = NULL; + + while (1) { + if (read_line(f_in, buf, bufsiz, rl_flags) == 0) + break; + + if (argc == maxargs-1) { + maxargs += MAX_ARGS; + if (!(argv = realloc_array(argv, char *, maxargs))) + out_of_memory("read_args"); + } + + if (dot_pos) { + if (request_p && request_len < 1024) { + int len = strlen(buf); + if (request_len) + request_p[0][request_len++] = ' '; + if (!(*request_p = realloc_array(*request_p, char, request_len + len + 1))) + out_of_memory("read_args"); + memcpy(*request_p + request_len, buf, len + 1); + request_len += len; + } + if (mod_name) + glob_expand_module(mod_name, buf, &argv, &argc, &maxargs); + else + glob_expand(buf, &argv, &argc, &maxargs); + } else { + if (!(p = strdup(buf))) + out_of_memory("read_args"); + argv[argc++] = p; + if (*p == '.' && p[1] == '\0') + dot_pos = argc; + } + } + argv[argc] = NULL; + + glob_expand(NULL, NULL, NULL, NULL); + + *argc_p = argc; + *argv_p = argv; +} + +BOOL io_start_buffering_out(int f_out) +{ + if (msgs2stderr && DEBUG_GTE(IO, 2)) + rprintf(FINFO, "[%s] io_start_buffering_out(%d)\n", who_am_i(), f_out); + + if (iobuf.out.buf) { + if (iobuf.out_fd == -1) + iobuf.out_fd = f_out; + else + assert(f_out == iobuf.out_fd); + return False; + } + + alloc_xbuf(&iobuf.out, ROUND_UP_1024(IO_BUFFER_SIZE * 2)); + iobuf.out_fd = f_out; + + return True; +} + +BOOL io_start_buffering_in(int f_in) +{ + if (msgs2stderr && DEBUG_GTE(IO, 2)) + rprintf(FINFO, "[%s] io_start_buffering_in(%d)\n", who_am_i(), f_in); + + if (iobuf.in.buf) { + if (iobuf.in_fd == -1) + iobuf.in_fd = f_in; + else + assert(f_in == iobuf.in_fd); + return False; + } + + alloc_xbuf(&iobuf.in, ROUND_UP_1024(IO_BUFFER_SIZE)); + iobuf.in_fd = f_in; + + return True; +} + +void io_end_buffering_in(BOOL free_buffers) +{ + if (msgs2stderr && DEBUG_GTE(IO, 2)) { + rprintf(FINFO, "[%s] io_end_buffering_in(IOBUF_%s_BUFS)\n", + who_am_i(), free_buffers ? "FREE" : "KEEP"); + } + + if (free_buffers) + free_xbuf(&iobuf.in); + else + iobuf.in.pos = iobuf.in.len = 0; + + iobuf.in_fd = -1; +} + +void io_end_buffering_out(BOOL free_buffers) +{ + if (msgs2stderr && DEBUG_GTE(IO, 2)) { + rprintf(FINFO, "[%s] io_end_buffering_out(IOBUF_%s_BUFS)\n", + who_am_i(), free_buffers ? "FREE" : "KEEP"); + } + + io_flush(FULL_FLUSH); + + if (free_buffers) { + free_xbuf(&iobuf.out); + free_xbuf(&iobuf.msg); + } + + iobuf.out_fd = -1; +} + +void maybe_flush_socket(int important) +{ + if (flist_eof && iobuf.out.buf && iobuf.out.len > iobuf.out_empty_len + && (important || time(NULL) - last_io_out >= 5)) + io_flush(NORMAL_FLUSH); +} + +/* Older rsync versions used to send either a MSG_NOOP (protocol 30) or a + * raw-data-based keep-alive (protocol 29), both of which implied forwarding of + * the message through the sender. Since the new timeout method does not need + * any forwarding, we just send an empty MSG_DATA message, which works with all + * rsync versions. This avoids any message forwarding, and leaves the raw-data + * stream alone (since we can never be quite sure if that stream is in the + * right state for a keep-alive message). */ +void maybe_send_keepalive(time_t now, int flags) +{ + if (flags & MSK_ACTIVE_RECEIVER) + last_io_in = now; /* Fudge things when we're working hard on the files. */ + + /* Early in the transfer (before the receiver forks) the receiving side doesn't + * care if it hasn't sent data in a while as long as it is receiving data (in + * fact, a pre-3.1.0 rsync would die if we tried to send it a keep alive during + * this time). So, if we're an early-receiving proc, just return and let the + * incoming data determine if we timeout. */ + if (!am_sender && !am_receiver && !am_generator) + return; + + if (now - last_io_out >= allowed_lull) { + /* The receiver is special: it only sends keep-alive messages if it is + * actively receiving data. Otherwise, it lets the generator timeout. */ + if (am_receiver && now - last_io_in >= io_timeout) + return; + + if (!iobuf.msg.len && iobuf.out.len == iobuf.out_empty_len) + send_msg(MSG_DATA, "", 0, 0); + if (!(flags & MSK_ALLOW_FLUSH)) { + /* Let the caller worry about writing out the data. */ + } else if (iobuf.msg.len) + perform_io(iobuf.msg.size - iobuf.msg.len + 1, PIO_NEED_MSGROOM); + else if (iobuf.out.len > iobuf.out_empty_len) + io_flush(NORMAL_FLUSH); + } +} + +void start_flist_forward(int ndx) +{ + write_int(iobuf.out_fd, ndx); + forward_flist_data = 1; +} + +void stop_flist_forward(void) +{ + forward_flist_data = 0; +} + +/* Read a message from a multiplexed source. */ +static void read_a_msg(void) +{ + char data[BIGPATHBUFLEN]; + int tag, val; + size_t msg_bytes; + + /* This ensures that perform_io() does not try to do any message reading + * until we've read all of the data for this message. We should also + * try to avoid calling things that will cause data to be written via + * perform_io() prior to this being reset to 1. */ + iobuf.in_multiplexed = -1; + + tag = raw_read_int(); + + msg_bytes = tag & 0xFFFFFF; + tag = (tag >> 24) - MPLEX_BASE; + + if (DEBUG_GTE(IO, 1) && msgs2stderr) + rprintf(FINFO, "[%s] got msg=%d, len=%ld\n", who_am_i(), (int)tag, (long)msg_bytes); + + switch (tag) { + case MSG_DATA: + assert(iobuf.raw_input_ends_before == 0); + /* Though this does not yet read the data, we do mark where in + * the buffer the msg data will end once it is read. It is + * possible that this points off the end of the buffer, in + * which case the gradual reading of the input stream will + * cause this value to wrap around and eventually become real. */ + if (msg_bytes) + iobuf.raw_input_ends_before = iobuf.in.pos + msg_bytes; + iobuf.in_multiplexed = 1; + break; + case MSG_STATS: + if (msg_bytes != sizeof stats.total_read || !am_generator) + goto invalid_msg; + raw_read_buf((char*)&stats.total_read, sizeof stats.total_read); + iobuf.in_multiplexed = 1; + break; + case MSG_REDO: + if (msg_bytes != 4 || !am_generator) + goto invalid_msg; + val = raw_read_int(); + iobuf.in_multiplexed = 1; + got_flist_entry_status(FES_REDO, val); + break; + case MSG_IO_ERROR: + if (msg_bytes != 4) + goto invalid_msg; + val = raw_read_int(); + iobuf.in_multiplexed = 1; + io_error |= val; + if (am_receiver) + send_msg_int(MSG_IO_ERROR, val); + break; + case MSG_IO_TIMEOUT: + if (msg_bytes != 4 || am_server || am_generator) + goto invalid_msg; + val = raw_read_int(); + iobuf.in_multiplexed = 1; + if (!io_timeout || io_timeout > val) { + if (INFO_GTE(MISC, 2)) + rprintf(FINFO, "Setting --timeout=%d to match server\n", val); + set_io_timeout(val); + } + break; + case MSG_NOOP: + /* Support protocol-30 keep-alive method. */ + if (msg_bytes != 0) + goto invalid_msg; + iobuf.in_multiplexed = 1; + if (am_sender) + maybe_send_keepalive(time(NULL), MSK_ALLOW_FLUSH); + break; + case MSG_DELETED: + if (msg_bytes >= sizeof data) + goto overflow; + if (am_generator) { + raw_read_buf(data, msg_bytes); + iobuf.in_multiplexed = 1; + send_msg(MSG_DELETED, data, msg_bytes, 1); + break; + } +#ifdef ICONV_OPTION + if (ic_recv != (iconv_t)-1) { + xbuf outbuf, inbuf; + char ibuf[512]; + int add_null = 0; + int flags = ICB_INCLUDE_BAD | ICB_INIT; + + INIT_CONST_XBUF(outbuf, data); + INIT_XBUF(inbuf, ibuf, 0, (size_t)-1); + + while (msg_bytes) { + size_t len = msg_bytes > sizeof ibuf - inbuf.len ? sizeof ibuf - inbuf.len : msg_bytes; + raw_read_buf(ibuf + inbuf.len, len); + inbuf.pos = 0; + inbuf.len += len; + if (!(msg_bytes -= len) && !ibuf[inbuf.len-1]) + inbuf.len--, add_null = 1; + if (iconvbufs(ic_send, &inbuf, &outbuf, flags) < 0) { + if (errno == E2BIG) + goto overflow; + /* Buffer ended with an incomplete char, so move the + * bytes to the start of the buffer and continue. */ + memmove(ibuf, ibuf + inbuf.pos, inbuf.len); + } + flags &= ~ICB_INIT; + } + if (add_null) { + if (outbuf.len == outbuf.size) + goto overflow; + outbuf.buf[outbuf.len++] = '\0'; + } + msg_bytes = outbuf.len; + } else +#endif + raw_read_buf(data, msg_bytes); + iobuf.in_multiplexed = 1; + /* A directory name was sent with the trailing null */ + if (msg_bytes > 0 && !data[msg_bytes-1]) + log_delete(data, S_IFDIR); + else { + data[msg_bytes] = '\0'; + log_delete(data, S_IFREG); + } + break; + case MSG_SUCCESS: + if (msg_bytes != 4) { + invalid_msg: + rprintf(FERROR, "invalid multi-message %d:%lu [%s%s]\n", + tag, (unsigned long)msg_bytes, who_am_i(), + inc_recurse ? "/inc" : ""); + exit_cleanup(RERR_STREAMIO); + } + val = raw_read_int(); + iobuf.in_multiplexed = 1; + if (am_generator) + got_flist_entry_status(FES_SUCCESS, val); + else + successful_send(val); + break; + case MSG_NO_SEND: + if (msg_bytes != 4) + goto invalid_msg; + val = raw_read_int(); + iobuf.in_multiplexed = 1; + if (am_generator) + got_flist_entry_status(FES_NO_SEND, val); + else + send_msg_int(MSG_NO_SEND, val); + break; + case MSG_ERROR_SOCKET: + case MSG_ERROR_UTF8: + case MSG_CLIENT: + case MSG_LOG: + if (!am_generator) + goto invalid_msg; + if (tag == MSG_ERROR_SOCKET) + msgs2stderr = 1; + /* FALL THROUGH */ + case MSG_INFO: + case MSG_ERROR: + case MSG_ERROR_XFER: + case MSG_WARNING: + if (msg_bytes >= sizeof data) { + overflow: + rprintf(FERROR, + "multiplexing overflow %d:%lu [%s%s]\n", + tag, (unsigned long)msg_bytes, who_am_i(), + inc_recurse ? "/inc" : ""); + exit_cleanup(RERR_STREAMIO); + } + raw_read_buf(data, msg_bytes); + /* We don't set in_multiplexed value back to 1 before writing this message + * because the write might loop back and read yet another message, over and + * over again, while waiting for room to put the message in the msg buffer. */ + rwrite((enum logcode)tag, data, msg_bytes, !am_generator); + iobuf.in_multiplexed = 1; + if (first_message) { + if (list_only && !am_sender && tag == 1 && msg_bytes < sizeof data) { + data[msg_bytes] = '\0'; + check_for_d_option_error(data); + } + first_message = 0; + } + break; + case MSG_ERROR_EXIT: + if (msg_bytes == 4) + val = raw_read_int(); + else if (msg_bytes == 0) + val = 0; + else + goto invalid_msg; + iobuf.in_multiplexed = 1; + if (DEBUG_GTE(EXIT, 3)) + rprintf(FINFO, "[%s] got MSG_ERROR_EXIT with %ld bytes\n", who_am_i(), (long)msg_bytes); + if (msg_bytes == 0) { + if (!am_sender && !am_generator) { + if (DEBUG_GTE(EXIT, 3)) { + rprintf(FINFO, "[%s] sending MSG_ERROR_EXIT (len 0)\n", + who_am_i()); + } + send_msg(MSG_ERROR_EXIT, "", 0, 0); + io_flush(FULL_FLUSH); + } + } else if (protocol_version >= 31) { + if (am_generator || am_receiver) { + if (DEBUG_GTE(EXIT, 3)) { + rprintf(FINFO, "[%s] sending MSG_ERROR_EXIT with exit_code %d\n", + who_am_i(), val); + } + send_msg_int(MSG_ERROR_EXIT, val); + } else { + if (DEBUG_GTE(EXIT, 3)) { + rprintf(FINFO, "[%s] sending MSG_ERROR_EXIT (len 0)\n", + who_am_i()); + } + send_msg(MSG_ERROR_EXIT, "", 0, 0); + } + } + /* Send a negative linenum so that we don't end up + * with a duplicate exit message. */ + _exit_cleanup(val, __FILE__, 0 - __LINE__); + default: + rprintf(FERROR, "unexpected tag %d [%s%s]\n", + tag, who_am_i(), inc_recurse ? "/inc" : ""); + exit_cleanup(RERR_STREAMIO); + } + + assert(iobuf.in_multiplexed > 0); +} + +static void drain_multiplex_messages(void) +{ + while (IN_MULTIPLEXED_AND_READY && iobuf.in.len) { + if (iobuf.raw_input_ends_before) { + size_t raw_len = iobuf.raw_input_ends_before - iobuf.in.pos; + iobuf.raw_input_ends_before = 0; + if (raw_len >= iobuf.in.len) { + iobuf.in.len = 0; + break; + } + iobuf.in.len -= raw_len; + if ((iobuf.in.pos += raw_len) >= iobuf.in.size) + iobuf.in.pos -= iobuf.in.size; + } + read_a_msg(); + } +} + +void wait_for_receiver(void) +{ + if (!iobuf.raw_input_ends_before) + read_a_msg(); + + if (iobuf.raw_input_ends_before) { + int ndx = read_int(iobuf.in_fd); + if (ndx < 0) { + switch (ndx) { + case NDX_FLIST_EOF: + flist_eof = 1; + if (DEBUG_GTE(FLIST, 3)) + rprintf(FINFO, "[%s] flist_eof=1\n", who_am_i()); + break; + case NDX_DONE: + msgdone_cnt++; + break; + default: + exit_cleanup(RERR_STREAMIO); + } + } else { + struct file_list *flist; + flist_receiving_enabled = False; + if (DEBUG_GTE(FLIST, 2)) { + rprintf(FINFO, "[%s] receiving flist for dir %d\n", + who_am_i(), ndx); + } + flist = recv_file_list(iobuf.in_fd); + flist->parent_ndx = ndx; +#ifdef SUPPORT_HARD_LINKS + if (preserve_hard_links) + match_hard_links(flist); +#endif + flist_receiving_enabled = True; + } + } +} + +unsigned short read_shortint(int f) +{ + char b[2]; + read_buf(f, b, 2); + return (UVAL(b, 1) << 8) + UVAL(b, 0); +} + +int32 read_int(int f) +{ + char b[4]; + int32 num; + + read_buf(f, b, 4); + num = IVAL(b, 0); +#if SIZEOF_INT32 > 4 + if (num & (int32)0x80000000) + num |= ~(int32)0xffffffff; +#endif + return num; +} + +int32 read_varint(int f) +{ + union { + char b[5]; + int32 x; + } u; + uchar ch; + int extra; + + u.x = 0; + ch = read_byte(f); + extra = int_byte_extra[ch / 4]; + if (extra) { + uchar bit = ((uchar)1<<(8-extra)); + if (extra >= (int)sizeof u.b) { + rprintf(FERROR, "Overflow in read_varint()\n"); + exit_cleanup(RERR_STREAMIO); + } + read_buf(f, u.b, extra); + u.b[extra] = ch & (bit-1); + } else + u.b[0] = ch; +#if CAREFUL_ALIGNMENT + u.x = IVAL(u.b,0); +#endif +#if SIZEOF_INT32 > 4 + if (u.x & (int32)0x80000000) + u.x |= ~(int32)0xffffffff; +#endif + return u.x; +} + +int64 read_varlong(int f, uchar min_bytes) +{ + union { + char b[9]; + int64 x; + } u; + char b2[8]; + int extra; + +#if SIZEOF_INT64 < 8 + memset(u.b, 0, 8); +#else + u.x = 0; +#endif + read_buf(f, b2, min_bytes); + memcpy(u.b, b2+1, min_bytes-1); + extra = int_byte_extra[CVAL(b2, 0) / 4]; + if (extra) { + uchar bit = ((uchar)1<<(8-extra)); + if (min_bytes + extra > (int)sizeof u.b) { + rprintf(FERROR, "Overflow in read_varlong()\n"); + exit_cleanup(RERR_STREAMIO); + } + read_buf(f, u.b + min_bytes - 1, extra); + u.b[min_bytes + extra - 1] = CVAL(b2, 0) & (bit-1); +#if SIZEOF_INT64 < 8 + if (min_bytes + extra > 5 || u.b[4] || CVAL(u.b,3) & 0x80) { + rprintf(FERROR, "Integer overflow: attempted 64-bit offset\n"); + exit_cleanup(RERR_UNSUPPORTED); + } +#endif + } else + u.b[min_bytes + extra - 1] = CVAL(b2, 0); +#if SIZEOF_INT64 < 8 + u.x = IVAL(u.b,0); +#elif CAREFUL_ALIGNMENT + u.x = IVAL64(u.b,0); +#endif + return u.x; +} + +int64 read_longint(int f) +{ +#if SIZEOF_INT64 >= 8 + char b[9]; +#endif + int32 num = read_int(f); + + if (num != (int32)0xffffffff) + return num; + +#if SIZEOF_INT64 < 8 + rprintf(FERROR, "Integer overflow: attempted 64-bit offset\n"); + exit_cleanup(RERR_UNSUPPORTED); +#else + read_buf(f, b, 8); + return IVAL(b,0) | (((int64)IVAL(b,4))<<32); +#endif +} + +void read_buf(int f, char *buf, size_t len) +{ + if (f != iobuf.in_fd) { + if (safe_read(f, buf, len) != len) + whine_about_eof(False); /* Doesn't return. */ + goto batch_copy; + } + + if (!IN_MULTIPLEXED) { + raw_read_buf(buf, len); + total_data_read += len; + if (forward_flist_data) + write_buf(iobuf.out_fd, buf, len); + batch_copy: + if (f == write_batch_monitor_in) + safe_write(batch_fd, buf, len); + return; + } + + while (1) { + size_t siz; + + while (!iobuf.raw_input_ends_before) + read_a_msg(); + + siz = MIN(len, iobuf.raw_input_ends_before - iobuf.in.pos); + if (siz >= iobuf.in.size) + siz = iobuf.in.size; + raw_read_buf(buf, siz); + total_data_read += siz; + + if (forward_flist_data) + write_buf(iobuf.out_fd, buf, siz); + + if (f == write_batch_monitor_in) + safe_write(batch_fd, buf, siz); + + if ((len -= siz) == 0) + break; + buf += siz; + } +} + +void read_sbuf(int f, char *buf, size_t len) +{ + read_buf(f, buf, len); + buf[len] = '\0'; +} + +uchar read_byte(int f) +{ + uchar c; + read_buf(f, (char*)&c, 1); + return c; +} + +int read_vstring(int f, char *buf, int bufsize) +{ + int len = read_byte(f); + + if (len & 0x80) + len = (len & ~0x80) * 0x100 + read_byte(f); + + if (len >= bufsize) { + rprintf(FERROR, "over-long vstring received (%d > %d)\n", + len, bufsize - 1); + return -1; + } + + if (len) + read_buf(f, buf, len); + buf[len] = '\0'; + return len; +} + +/* Populate a sum_struct with values from the socket. This is + * called by both the sender and the receiver. */ +void read_sum_head(int f, struct sum_struct *sum) +{ + int32 max_blength = protocol_version < 30 ? OLD_MAX_BLOCK_SIZE : MAX_BLOCK_SIZE; + sum->count = read_int(f); + if (sum->count < 0) { + rprintf(FERROR, "Invalid checksum count %ld [%s]\n", + (long)sum->count, who_am_i()); + exit_cleanup(RERR_PROTOCOL); + } + sum->blength = read_int(f); + if (sum->blength < 0 || sum->blength > max_blength) { + rprintf(FERROR, "Invalid block length %ld [%s]\n", + (long)sum->blength, who_am_i()); + exit_cleanup(RERR_PROTOCOL); + } + sum->s2length = protocol_version < 27 ? csum_length : (int)read_int(f); + if (sum->s2length < 0 || sum->s2length > MAX_DIGEST_LEN) { + rprintf(FERROR, "Invalid checksum length %d [%s]\n", + sum->s2length, who_am_i()); + exit_cleanup(RERR_PROTOCOL); + } + sum->remainder = read_int(f); + if (sum->remainder < 0 || sum->remainder > sum->blength) { + rprintf(FERROR, "Invalid remainder length %ld [%s]\n", + (long)sum->remainder, who_am_i()); + exit_cleanup(RERR_PROTOCOL); + } +} + +/* Send the values from a sum_struct over the socket. Set sum to + * NULL if there are no checksums to send. This is called by both + * the generator and the sender. */ +void write_sum_head(int f, struct sum_struct *sum) +{ + static struct sum_struct null_sum; + + if (sum == NULL) + sum = &null_sum; + + write_int(f, sum->count); + write_int(f, sum->blength); + if (protocol_version >= 27) + write_int(f, sum->s2length); + write_int(f, sum->remainder); +} + +/* Sleep after writing to limit I/O bandwidth usage. + * + * @todo Rather than sleeping after each write, it might be better to + * use some kind of averaging. The current algorithm seems to always + * use a bit less bandwidth than specified, because it doesn't make up + * for slow periods. But arguably this is a feature. In addition, we + * ought to take the time used to write the data into account. + * + * During some phases of big transfers (file FOO is uptodate) this is + * called with a small bytes_written every time. As the kernel has to + * round small waits up to guarantee that we actually wait at least the + * requested number of microseconds, this can become grossly inaccurate. + * We therefore keep track of the bytes we've written over time and only + * sleep when the accumulated delay is at least 1 tenth of a second. */ +static void sleep_for_bwlimit(int bytes_written) +{ + static struct timeval prior_tv; + static long total_written = 0; + struct timeval tv, start_tv; + long elapsed_usec, sleep_usec; + +#define ONE_SEC 1000000L /* # of microseconds in a second */ + + total_written += bytes_written; + + gettimeofday(&start_tv, NULL); + if (prior_tv.tv_sec) { + elapsed_usec = (start_tv.tv_sec - prior_tv.tv_sec) * ONE_SEC + + (start_tv.tv_usec - prior_tv.tv_usec); + total_written -= (int64)elapsed_usec * bwlimit / (ONE_SEC/1024); + if (total_written < 0) + total_written = 0; + } + + sleep_usec = total_written * (ONE_SEC/1024) / bwlimit; + if (sleep_usec < ONE_SEC / 10) { + prior_tv = start_tv; + return; + } + + tv.tv_sec = sleep_usec / ONE_SEC; + tv.tv_usec = sleep_usec % ONE_SEC; + select(0, NULL, NULL, NULL, &tv); + + gettimeofday(&prior_tv, NULL); + elapsed_usec = (prior_tv.tv_sec - start_tv.tv_sec) * ONE_SEC + + (prior_tv.tv_usec - start_tv.tv_usec); + total_written = (sleep_usec - elapsed_usec) * bwlimit / (ONE_SEC/1024); +} + +void io_flush(int flush_it_all) +{ + if (iobuf.out.len > iobuf.out_empty_len) { + if (flush_it_all) /* FULL_FLUSH: flush everything in the output buffers */ + perform_io(iobuf.out.size - iobuf.out_empty_len, PIO_NEED_OUTROOM); + else /* NORMAL_FLUSH: flush at least 1 byte */ + perform_io(iobuf.out.size - iobuf.out.len + 1, PIO_NEED_OUTROOM); + } + if (iobuf.msg.len) + perform_io(iobuf.msg.size, PIO_NEED_MSGROOM); +} + +void write_shortint(int f, unsigned short x) +{ + char b[2]; + b[0] = (char)x; + b[1] = (char)(x >> 8); + write_buf(f, b, 2); +} + +void write_int(int f, int32 x) +{ + char b[4]; + SIVAL(b, 0, x); + write_buf(f, b, 4); +} + +void write_varint(int f, int32 x) +{ + char b[5]; + uchar bit; + int cnt = 4; + + SIVAL(b, 1, x); + + while (cnt > 1 && b[cnt] == 0) + cnt--; + bit = ((uchar)1<<(7-cnt+1)); + if (CVAL(b, cnt) >= bit) { + cnt++; + *b = ~(bit-1); + } else if (cnt > 1) + *b = b[cnt] | ~(bit*2-1); + else + *b = b[cnt]; + + write_buf(f, b, cnt); +} + +void write_varlong(int f, int64 x, uchar min_bytes) +{ + char b[9]; + uchar bit; + int cnt = 8; + +#if SIZEOF_INT64 >= 8 + SIVAL64(b, 1, x); +#else + SIVAL(b, 1, x); + if (x <= 0x7FFFFFFF && x >= 0) + memset(b + 5, 0, 4); + else { + rprintf(FERROR, "Integer overflow: attempted 64-bit offset\n"); + exit_cleanup(RERR_UNSUPPORTED); + } +#endif + + while (cnt > min_bytes && b[cnt] == 0) + cnt--; + bit = ((uchar)1<<(7-cnt+min_bytes)); + if (CVAL(b, cnt) >= bit) { + cnt++; + *b = ~(bit-1); + } else if (cnt > min_bytes) + *b = b[cnt] | ~(bit*2-1); + else + *b = b[cnt]; + + write_buf(f, b, cnt); +} + +/* + * Note: int64 may actually be a 32-bit type if ./configure couldn't find any + * 64-bit types on this platform. + */ +void write_longint(int f, int64 x) +{ + char b[12], * const s = b+4; + + SIVAL(s, 0, x); + if (x <= 0x7FFFFFFF && x >= 0) { + write_buf(f, s, 4); + return; + } + +#if SIZEOF_INT64 < 8 + rprintf(FERROR, "Integer overflow: attempted 64-bit offset\n"); + exit_cleanup(RERR_UNSUPPORTED); +#else + memset(b, 0xFF, 4); + SIVAL(s, 4, x >> 32); + write_buf(f, b, 12); +#endif +} + +void write_bigbuf(int f, const char *buf, size_t len) +{ + size_t half_max = (iobuf.out.size - iobuf.out_empty_len) / 2; + + while (len > half_max + 1024) { + write_buf(f, buf, half_max); + buf += half_max; + len -= half_max; + } + + write_buf(f, buf, len); +} + +void write_buf(int f, const char *buf, size_t len) +{ + size_t pos, siz; + + if (f != iobuf.out_fd) { + safe_write(f, buf, len); + goto batch_copy; + } + + if (iobuf.out.len + len > iobuf.out.size) + perform_io(len, PIO_NEED_OUTROOM); + + pos = iobuf.out.pos + iobuf.out.len; /* Must be set after any flushing. */ + if (pos >= iobuf.out.size) + pos -= iobuf.out.size; + + /* Handle a split copy if we wrap around the end of the circular buffer. */ + if (pos >= iobuf.out.pos && (siz = iobuf.out.size - pos) < len) { + memcpy(iobuf.out.buf + pos, buf, siz); + memcpy(iobuf.out.buf, buf + siz, len - siz); + } else + memcpy(iobuf.out.buf + pos, buf, len); + + iobuf.out.len += len; + total_data_written += len; + + batch_copy: + if (f == write_batch_monitor_out) + safe_write(batch_fd, buf, len); +} + +/* Write a string to the connection */ +void write_sbuf(int f, const char *buf) +{ + write_buf(f, buf, strlen(buf)); +} + +void write_byte(int f, uchar c) +{ + write_buf(f, (char *)&c, 1); +} + +void write_vstring(int f, const char *str, int len) +{ + uchar lenbuf[3], *lb = lenbuf; + + if (len > 0x7F) { + if (len > 0x7FFF) { + rprintf(FERROR, + "attempting to send over-long vstring (%d > %d)\n", + len, 0x7FFF); + exit_cleanup(RERR_PROTOCOL); + } + *lb++ = len / 0x100 + 0x80; + } + *lb = len; + + write_buf(f, (char*)lenbuf, lb - lenbuf + 1); + if (len) + write_buf(f, str, len); +} + +/* Send a file-list index using a byte-reduction method. */ +void write_ndx(int f, int32 ndx) +{ + static int32 prev_positive = -1, prev_negative = 1; + int32 diff, cnt = 0; + char b[6]; + + if (protocol_version < 30 || read_batch) { + write_int(f, ndx); + return; + } + + /* Send NDX_DONE as a single-byte 0 with no side effects. Send + * negative nums as a positive after sending a leading 0xFF. */ + if (ndx >= 0) { + diff = ndx - prev_positive; + prev_positive = ndx; + } else if (ndx == NDX_DONE) { + *b = 0; + write_buf(f, b, 1); + return; + } else { + b[cnt++] = (char)0xFF; + ndx = -ndx; + diff = ndx - prev_negative; + prev_negative = ndx; + } + + /* A diff of 1 - 253 is sent as a one-byte diff; a diff of 254 - 32767 + * or 0 is sent as a 0xFE + a two-byte diff; otherwise we send 0xFE + * & all 4 bytes of the (non-negative) num with the high-bit set. */ + if (diff < 0xFE && diff > 0) + b[cnt++] = (char)diff; + else if (diff < 0 || diff > 0x7FFF) { + b[cnt++] = (char)0xFE; + b[cnt++] = (char)((ndx >> 24) | 0x80); + b[cnt++] = (char)ndx; + b[cnt++] = (char)(ndx >> 8); + b[cnt++] = (char)(ndx >> 16); + } else { + b[cnt++] = (char)0xFE; + b[cnt++] = (char)(diff >> 8); + b[cnt++] = (char)diff; + } + write_buf(f, b, cnt); +} + +/* Receive a file-list index using a byte-reduction method. */ +int32 read_ndx(int f) +{ + static int32 prev_positive = -1, prev_negative = 1; + int32 *prev_ptr, num; + char b[4]; + + if (protocol_version < 30) + return read_int(f); + + read_buf(f, b, 1); + if (CVAL(b, 0) == 0xFF) { + read_buf(f, b, 1); + prev_ptr = &prev_negative; + } else if (CVAL(b, 0) == 0) + return NDX_DONE; + else + prev_ptr = &prev_positive; + if (CVAL(b, 0) == 0xFE) { + read_buf(f, b, 2); + if (CVAL(b, 0) & 0x80) { + b[3] = CVAL(b, 0) & ~0x80; + b[0] = b[1]; + read_buf(f, b+1, 2); + num = IVAL(b, 0); + } else + num = (UVAL(b,0)<<8) + UVAL(b,1) + *prev_ptr; + } else + num = UVAL(b, 0) + *prev_ptr; + *prev_ptr = num; + if (prev_ptr == &prev_negative) + num = -num; + return num; +} + +/* Read a line of up to bufsiz-1 characters into buf. Strips + * the (required) trailing newline and all carriage returns. + * Returns 1 for success; 0 for I/O error or truncation. */ +int read_line_old(int fd, char *buf, size_t bufsiz, int eof_ok) +{ + assert(fd != iobuf.in_fd); + bufsiz--; /* leave room for the null */ + while (bufsiz > 0) { + if (safe_read(fd, buf, 1) == 0) { + if (eof_ok) + break; + return 0; + } + if (*buf == '\0') + return 0; + if (*buf == '\n') + break; + if (*buf != '\r') { + buf++; + bufsiz--; + } + } + *buf = '\0'; + return bufsiz > 0; +} + +void io_printf(int fd, const char *format, ...) +{ + va_list ap; + char buf[BIGPATHBUFLEN]; + int len; + + va_start(ap, format); + len = vsnprintf(buf, sizeof buf, format, ap); + va_end(ap); + + if (len < 0) + exit_cleanup(RERR_PROTOCOL); + + if (len > (int)sizeof buf) { + rprintf(FERROR, "io_printf() was too long for the buffer.\n"); + exit_cleanup(RERR_PROTOCOL); + } + + write_sbuf(fd, buf); +} + +/* Setup for multiplexing a MSG_* stream with the data stream. */ +void io_start_multiplex_out(int fd) +{ + io_flush(FULL_FLUSH); + + if (msgs2stderr && DEBUG_GTE(IO, 2)) + rprintf(FINFO, "[%s] io_start_multiplex_out(%d)\n", who_am_i(), fd); + + if (!iobuf.msg.buf) + alloc_xbuf(&iobuf.msg, ROUND_UP_1024(IO_BUFFER_SIZE)); + + iobuf.out_empty_len = 4; /* See also OUT_MULTIPLEXED */ + io_start_buffering_out(fd); + got_kill_signal = 0; + + iobuf.raw_data_header_pos = iobuf.out.pos + iobuf.out.len; + iobuf.out.len += 4; +} + +/* Setup for multiplexing a MSG_* stream with the data stream. */ +void io_start_multiplex_in(int fd) +{ + if (msgs2stderr && DEBUG_GTE(IO, 2)) + rprintf(FINFO, "[%s] io_start_multiplex_in(%d)\n", who_am_i(), fd); + + iobuf.in_multiplexed = 1; /* See also IN_MULTIPLEXED */ + io_start_buffering_in(fd); +} + +int io_end_multiplex_in(int mode) +{ + int ret = iobuf.in_multiplexed ? iobuf.in_fd : -1; + + if (msgs2stderr && DEBUG_GTE(IO, 2)) + rprintf(FINFO, "[%s] io_end_multiplex_in(mode=%d)\n", who_am_i(), mode); + + iobuf.in_multiplexed = 0; + if (mode == MPLX_SWITCHING) + iobuf.raw_input_ends_before = 0; + else + assert(iobuf.raw_input_ends_before == 0); + if (mode != MPLX_TO_BUFFERED) + io_end_buffering_in(mode); + + return ret; +} + +int io_end_multiplex_out(int mode) +{ + int ret = iobuf.out_empty_len ? iobuf.out_fd : -1; + + if (msgs2stderr && DEBUG_GTE(IO, 2)) + rprintf(FINFO, "[%s] io_end_multiplex_out(mode=%d)\n", who_am_i(), mode); + + if (mode != MPLX_TO_BUFFERED) + io_end_buffering_out(mode); + else + io_flush(FULL_FLUSH); + + iobuf.out.len = 0; + iobuf.out_empty_len = 0; + if (got_kill_signal > 0) /* Just in case... */ + handle_kill_signal(False); + got_kill_signal = -1; + + return ret; +} + +void start_write_batch(int fd) +{ + /* Some communication has already taken place, but we don't + * enable batch writing until here so that we can write a + * canonical record of the communication even though the + * actual communication so far depends on whether a daemon + * is involved. */ + write_int(batch_fd, protocol_version); + if (protocol_version >= 30) + write_byte(batch_fd, compat_flags); + write_int(batch_fd, checksum_seed); + + if (am_sender) + write_batch_monitor_out = fd; + else + write_batch_monitor_in = fd; +} + +void stop_write_batch(void) +{ + write_batch_monitor_out = -1; + write_batch_monitor_in = -1; +} diff --git a/rsync/io.h b/rsync/io.h new file mode 100644 index 0000000..14c8489 --- /dev/null +++ b/rsync/io.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2007-2014 Wayne Davison + * + * 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, visit the http://fsf.org website. + */ + +extern int protocol_version; + +static inline int32 +read_varint30(int f) +{ + if (protocol_version < 30) + return read_int(f); + return read_varint(f); +} + +static inline int64 +read_varlong30(int f, uchar min_bytes) +{ + if (protocol_version < 30) + return read_longint(f); + return read_varlong(f, min_bytes); +} + +static inline void +write_varint30(int f, int32 x) +{ + if (protocol_version < 30) + write_int(f, x); + else + write_varint(f, x); +} + +static inline void +write_varlong30(int f, int64 x, uchar min_bytes) +{ + if (protocol_version < 30) + write_longint(f, x); + else + write_varlong(f, x, min_bytes); +} diff --git a/rsync/itypes.h b/rsync/itypes.h new file mode 100644 index 0000000..3949087 --- /dev/null +++ b/rsync/itypes.h @@ -0,0 +1,59 @@ +/* Inline functions for rsync. + * + * Copyright (C) 2007-2014 Wayne Davison + * + * 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, visit the http://fsf.org website. + */ + +static inline int +isDigit(const char *ptr) +{ + return isdigit(*(unsigned char *)ptr); +} + +static inline int +isPrint(const char *ptr) +{ + return isprint(*(unsigned char *)ptr); +} + +static inline int +isSpace(const char *ptr) +{ + return isspace(*(unsigned char *)ptr); +} + +static inline int +isLower(const char *ptr) +{ + return islower(*(unsigned char *)ptr); +} + +static inline int +isUpper(const char *ptr) +{ + return isupper(*(unsigned char *)ptr); +} + +static inline int +toLower(const char *ptr) +{ + return tolower(*(unsigned char *)ptr); +} + +static inline int +toUpper(const char *ptr) +{ + return toupper(*(unsigned char *)ptr); +} diff --git a/rsync/lib/addrinfo.h b/rsync/lib/addrinfo.h new file mode 100644 index 0000000..ee9f672 --- /dev/null +++ b/rsync/lib/addrinfo.h @@ -0,0 +1,180 @@ +/* +PostgreSQL Database Management System +(formerly known as Postgres, then as Postgres95) + +Portions Copyright (c) 1996-2005, The PostgreSQL Global Development Group + +Portions Copyright (c) 1994, The Regents of the University of California + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose, without fee, and without a written agreement +is hereby granted, provided that the above copyright notice and this paragraph +and the following two paragraphs appear in all copies. + +IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR +DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING +LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, +EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. + +THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS +ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATIONS +TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. + +*/ + +/*------------------------------------------------------------------------- + * + * getaddrinfo.h + * Support getaddrinfo() on platforms that don't have it. + * + * Note: we use our own routines on platforms that don't HAVE_STRUCT_ADDRINFO, + * whether or not the library routine getaddrinfo() can be found. This + * policy is needed because on some platforms a manually installed libbind.a + * may provide getaddrinfo(), yet the system headers may not provide the + * struct definitions needed to call it. To avoid conflict with the libbind + * definition in such cases, we rename our routines to pg_xxx() via macros. + * + * This code will also work on platforms where struct addrinfo is defined + * in the system headers but no getaddrinfo() can be located. + * + * Copyright (c) 2003-2007, PostgreSQL Global Development Group + * + *------------------------------------------------------------------------- + */ +#ifndef ADDRINFO_H +#define ADDRINFO_H + + +/* Various macros that ought to be in , but might not be */ + +#ifndef EAI_FAIL +#define EAI_BADFLAGS (-1) +#define EAI_NONAME (-2) +#define EAI_AGAIN (-3) +#define EAI_FAIL (-4) +#define EAI_FAMILY (-6) +#define EAI_SOCKTYPE (-7) +#define EAI_SERVICE (-8) +#define EAI_MEMORY (-10) +#define EAI_SYSTEM (-11) +#endif /* !EAI_FAIL */ + +#ifndef AI_PASSIVE +#define AI_PASSIVE 0x0001 +#endif + +#ifndef AI_NUMERICHOST +/* + * some platforms don't support AI_NUMERICHOST; define as zero if using + * the system version of getaddrinfo... + */ +#if defined(HAVE_STRUCT_ADDRINFO) && defined(HAVE_GETADDRINFO) +#define AI_NUMERICHOST 0 +#else +#define AI_NUMERICHOST 0x0004 +#endif +#endif + +#ifndef AI_CANONNAME +#if defined(HAVE_STRUCT_ADDRINFO) && defined(HAVE_GETADDRINFO) +#define AI_CANONNAME 0 +#else +#define AI_CANONNAME 0x0008 +#endif +#endif + +#ifndef AI_NUMERICSERV +#if defined(HAVE_STRUCT_ADDRINFO) && defined(HAVE_GETADDRINFO) +#define AI_NUMERICSERV 0 +#else +#define AI_NUMERICSERV 0x0010 +#endif +#endif + +#ifndef NI_NUMERICHOST +#define NI_NUMERICHOST 1 +#endif + +#ifndef NI_NUMERICSERV +#define NI_NUMERICSERV 2 +#endif + +#ifndef NI_NOFQDN +#define NI_NOFQDN 4 +#endif + +#ifndef NI_NAMEREQD +#define NI_NAMEREQD 8 +#endif + +#ifndef NI_DGRAM +#define NI_DGRAM 16 +#endif + + +#ifndef NI_MAXHOST +#define NI_MAXHOST 1025 +#endif + +#ifndef NI_MAXSERV +#define NI_MAXSERV 32 +#endif + +#ifndef HAVE_STRUCT_ADDRINFO +struct addrinfo +{ + int ai_flags; + int ai_family; + int ai_socktype; + int ai_protocol; + size_t ai_addrlen; + struct sockaddr *ai_addr; + char *ai_canonname; + struct addrinfo *ai_next; +}; +#endif /* !HAVE_STRUCT_ADDRINFO */ + +#ifndef HAVE_STRUCT_SOCKADDR_STORAGE +struct sockaddr_storage { + unsigned short ss_family; + unsigned long ss_align; + char ss_padding[128 - sizeof (unsigned long)]; +}; +#endif /* !HAVE_STRUCT_SOCKADDR_STORAGE */ + +#ifndef HAVE_GETADDRINFO + +/* Rename private copies per comments above */ +#ifdef getaddrinfo +#undef getaddrinfo +#endif +#define getaddrinfo pg_getaddrinfo + +#ifdef freeaddrinfo +#undef freeaddrinfo +#endif +#define freeaddrinfo pg_freeaddrinfo + +#ifdef gai_strerror +#undef gai_strerror +#endif +#define gai_strerror pg_gai_strerror + +#ifdef getnameinfo +#undef getnameinfo +#endif +#define getnameinfo pg_getnameinfo + +extern int getaddrinfo(const char *node, const char *service, + const struct addrinfo * hints, struct addrinfo ** res); +extern void freeaddrinfo(struct addrinfo * res); +extern const char *gai_strerror(int errcode); +extern int getnameinfo(const struct sockaddr * sa, socklen_t salen, + char *node, size_t nodelen, + char *service, size_t servicelen, int flags); +#endif /* !HAVE_GETADDRINFO */ + +#endif /* ADDRINFO_H */ diff --git a/rsync/lib/compat.c b/rsync/lib/compat.c new file mode 100644 index 0000000..dfe963c --- /dev/null +++ b/rsync/lib/compat.c @@ -0,0 +1,275 @@ +/* + * Reimplementations of standard functions for platforms that don't have them. + * + * Copyright (C) 1998 Andrew Tridgell + * Copyright (C) 2002 Martin Pool + * Copyright (C) 2004-2014 Wayne Davison + * + * 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, visit the http://fsf.org website. + */ + +#include "rsync.h" +#include "itypes.h" + +static char number_separator; + +#ifndef HAVE_STRDUP + char *strdup(char *s) +{ + int len = strlen(s) + 1; + char *ret = (char *)malloc(len); + if (ret) + memcpy(ret, s, len); + return ret; +} +#endif + +#ifndef HAVE_GETCWD + char *getcwd(char *buf, int size) +{ + return getwd(buf); +} +#endif + + +#ifndef HAVE_WAITPID + pid_t waitpid(pid_t pid, int *statptr, int options) +{ +#ifdef HAVE_WAIT4 + return wait4(pid, statptr, options, NULL); +#else + /* If wait4 is also not available, try wait3 for SVR3 variants */ + /* Less ideal because can't actually request a specific pid */ + /* At least the WNOHANG option is supported */ + /* Code borrowed from apache fragment written by dwd@bell-labs.com */ + int tmp_pid, dummystat;; + if (kill(pid, 0) == -1) { + errno = ECHILD; + return -1; + } + if (statptr == NULL) + statptr = &dummystat; + while (((tmp_pid = wait3(statptr, options, 0)) != pid) && + (tmp_pid != -1) && (tmp_pid != 0) && (pid != -1)) + ; + return tmp_pid; +#endif +} +#endif + + +#ifndef HAVE_MEMMOVE + void *memmove(void *dest, const void *src, size_t n) +{ + bcopy((char *) src, (char *) dest, n); + return dest; +} +#endif + +#ifndef HAVE_STRPBRK +/** + * Find the first ocurrence in @p s of any character in @p accept. + * + * Derived from glibc + **/ + char *strpbrk(const char *s, const char *accept) +{ + while (*s != '\0') { + const char *a = accept; + while (*a != '\0') { + if (*a++ == *s) return (char *)s; + } + ++s; + } + + return NULL; +} +#endif + + +#ifndef HAVE_STRLCPY +/** + * Like strncpy but does not 0 fill the buffer and always null + * terminates. + * + * @param bufsize is the size of the destination buffer. + * + * @return index of the terminating byte. + **/ + size_t strlcpy(char *d, const char *s, size_t bufsize) +{ + size_t len = strlen(s); + size_t ret = len; + if (bufsize > 0) { + if (len >= bufsize) + len = bufsize-1; + memcpy(d, s, len); + d[len] = 0; + } + return ret; +} +#endif + +#ifndef HAVE_STRLCAT +/** + * Like strncat() but does not 0 fill the buffer and always null + * terminates. + * + * @param bufsize length of the buffer, which should be one more than + * the maximum resulting string length. + **/ + size_t strlcat(char *d, const char *s, size_t bufsize) +{ + size_t len1 = strlen(d); + size_t len2 = strlen(s); + size_t ret = len1 + len2; + + if (len1 < bufsize - 1) { + if (len2 >= bufsize - len1) + len2 = bufsize - len1 - 1; + memcpy(d+len1, s, len2); + d[len1+len2] = 0; + } + return ret; +} +#endif + +/* some systems don't take the 2nd argument */ +int sys_gettimeofday(struct timeval *tv) +{ +#ifdef HAVE_GETTIMEOFDAY_TZ + return gettimeofday(tv, NULL); +#else + return gettimeofday(tv); +#endif +} + +#define HUMANIFY(mult) \ + do { \ + if (num >= mult || num <= -mult) { \ + double dnum = (double)num / mult; \ + char units; \ + if (num < 0) \ + dnum = -dnum; \ + if (dnum < mult) \ + units = 'K'; \ + else if ((dnum /= mult) < mult) \ + units = 'M'; \ + else if ((dnum /= mult) < mult) \ + units = 'G'; \ + else { \ + dnum /= mult; \ + units = 'T'; \ + } \ + if (num < 0) \ + dnum = -dnum; \ + snprintf(bufs[n], sizeof bufs[0], "%.2f%c", dnum, units); \ + return bufs[n]; \ + } \ + } while (0) + +/* Return the int64 number as a string. If the human_flag arg is non-zero, + * we may output the number in K, M, G, or T units. If we don't add a unit + * suffix, we will append the fract string, if it is non-NULL. We can + * return up to 4 buffers at a time. */ +char *do_big_num(int64 num, int human_flag, const char *fract) +{ + static char bufs[4][128]; /* more than enough room */ + static unsigned int n; + char *s; + int len, negated; + + if (human_flag && !number_separator) { + char buf[32]; + snprintf(buf, sizeof buf, "%f", 3.14); + if (strchr(buf, '.') != NULL) + number_separator = ','; + else + number_separator = '.'; + } + + n = (n + 1) % (sizeof bufs / sizeof bufs[0]); + + if (human_flag > 1) { + if (human_flag == 2) + HUMANIFY(1000); + else + HUMANIFY(1024); + } + + s = bufs[n] + sizeof bufs[0] - 1; + if (fract) { + len = strlen(fract); + s -= len; + strlcpy(s, fract, len + 1); + } else + *s = '\0'; + + len = 0; + + if (!num) + *--s = '0'; + if (num < 0) { + /* A maximum-size negated number can't fit as a positive, + * so do one digit in negated form to start us off. */ + *--s = (char)(-(num % 10)) + '0'; + num = -(num / 10); + len++; + negated = 1; + } else + negated = 0; + + while (num) { + if (human_flag) { + if (len == 3) { + *--s = number_separator; + len = 1; + } else + len++; + } + *--s = (char)(num % 10) + '0'; + num /= 10; + } + + if (negated) + *--s = '-'; + + return s; +} + +/* Return the double number as a string. If the human_flag option is > 1, + * we may output the number in K, M, G, or T units. The buffer we use for + * our result is either a single static buffer defined here, or a buffer + * we get from do_big_num(). */ +char *do_big_dnum(double dnum, int human_flag, int decimal_digits) +{ + static char tmp_buf[128]; +#if SIZEOF_INT64 >= 8 + char *fract; + + snprintf(tmp_buf, sizeof tmp_buf, "%.*f", decimal_digits, dnum); + + if (!human_flag || (dnum < 1000.0 && dnum > -1000.0)) + return tmp_buf; + + for (fract = tmp_buf+1; isDigit(fract); fract++) {} + + return do_big_num((int64)dnum, human_flag, fract); +#else + /* A big number might lose digits converting to a too-short int64, + * so let's just return the raw double conversion. */ + snprintf(tmp_buf, sizeof tmp_buf, "%.*f", decimal_digits, dnum); + return tmp_buf; +#endif +} diff --git a/rsync/lib/dummy.in b/rsync/lib/dummy.in new file mode 100644 index 0000000..3b26a20 --- /dev/null +++ b/rsync/lib/dummy.in @@ -0,0 +1,2 @@ +This is a dummy file to ensure that the lib directory gets created +by configure when a VPATH is used. diff --git a/rsync/lib/getaddrinfo.c b/rsync/lib/getaddrinfo.c new file mode 100644 index 0000000..96d7a2b --- /dev/null +++ b/rsync/lib/getaddrinfo.c @@ -0,0 +1,504 @@ +/* +PostgreSQL Database Management System +(formerly known as Postgres, then as Postgres95) + +Portions Copyright (c) 1996-2005, The PostgreSQL Global Development Group + +Portions Copyright (c) 1994, The Regents of the University of California + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose, without fee, and without a written agreement +is hereby granted, provided that the above copyright notice and this paragraph +and the following two paragraphs appear in all copies. + +IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR +DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING +LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, +EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. + +THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS +ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATIONS +TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. + +*/ + +/*------------------------------------------------------------------------- + * + * getaddrinfo.c + * Support getaddrinfo() on platforms that don't have it. + * + * We also supply getnameinfo() here, assuming that the platform will have + * it if and only if it has getaddrinfo(). If this proves false on some + * platform, we'll need to split this file and provide a separate configure + * test for getnameinfo(). + * + * Copyright (c) 2003-2007, PostgreSQL Global Development Group + * + * Copyright (C) 2007 Jeremy Allison. + * Modified to return multiple IPv4 addresses for Samba. + * + *------------------------------------------------------------------------- + */ + +#include "rsync.h" + +#ifndef SMB_MALLOC +#define SMB_MALLOC(s) malloc(s) +#endif + +#ifndef SMB_STRDUP +#define SMB_STRDUP(s) strdup(s) +#endif + +#ifndef HOST_NAME_MAX +#define HOST_NAME_MAX 255 +#endif + +static int check_hostent_err(struct hostent *hp) +{ +#ifndef INET6 + extern int h_errno; +#endif + if (!hp) { + switch (h_errno) { + case HOST_NOT_FOUND: + case NO_DATA: + return EAI_NONAME; + case TRY_AGAIN: + return EAI_AGAIN; + case NO_RECOVERY: + default: + return EAI_FAIL; + } + } + if (!hp->h_name || hp->h_addrtype != AF_INET) { + return EAI_FAIL; + } + return 0; +} + +static char *canon_name_from_hostent(struct hostent *hp, + int *perr) +{ + char *ret = NULL; + + *perr = check_hostent_err(hp); + if (*perr) { + return NULL; + } + ret = SMB_STRDUP(hp->h_name); + if (!ret) { + *perr = EAI_MEMORY; + } + return ret; +} + +static char *get_my_canon_name(int *perr) +{ + char name[HOST_NAME_MAX+1]; + + if (gethostname(name, HOST_NAME_MAX) == -1) { + *perr = EAI_FAIL; + return NULL; + } + /* Ensure null termination. */ + name[HOST_NAME_MAX] = '\0'; + return canon_name_from_hostent(gethostbyname(name), perr); +} + +static char *get_canon_name_from_addr(struct in_addr ip, + int *perr) +{ + return canon_name_from_hostent( + gethostbyaddr((void *)&ip, sizeof ip, AF_INET), + perr); +} + +static struct addrinfo *alloc_entry(const struct addrinfo *hints, + struct in_addr ip, + unsigned short port) +{ + struct sockaddr_in *psin = NULL; + struct addrinfo *ai = SMB_MALLOC(sizeof(*ai)); + + if (!ai) { + return NULL; + } + memset(ai, '\0', sizeof(*ai)); + + psin = SMB_MALLOC(sizeof(*psin)); + if (!psin) { + free(ai); + return NULL; + } + + memset(psin, '\0', sizeof(*psin)); + + psin->sin_family = AF_INET; + psin->sin_port = htons(port); + psin->sin_addr = ip; + + ai->ai_flags = 0; + ai->ai_family = AF_INET; + ai->ai_socktype = hints->ai_socktype; + ai->ai_protocol = hints->ai_protocol; + ai->ai_addrlen = sizeof(*psin); + ai->ai_addr = (struct sockaddr *) psin; + ai->ai_canonname = NULL; + ai->ai_next = NULL; + + return ai; +} + +/* + * get address info for a single ipv4 address. + * + * Bugs: - servname can only be a number, not text. + */ + +static int getaddr_info_single_addr(const char *service, + uint32 addr, + const struct addrinfo *hints, + struct addrinfo **res) +{ + + struct addrinfo *ai = NULL; + struct in_addr ip; + unsigned short port = 0; + + if (service) { + port = (unsigned short)atoi(service); + } + ip.s_addr = htonl(addr); + + ai = alloc_entry(hints, ip, port); + if (!ai) { + return EAI_MEMORY; + } + + /* If we're asked for the canonical name, + * make sure it returns correctly. */ + if (!(hints->ai_flags & AI_NUMERICSERV) && + hints->ai_flags & AI_CANONNAME) { + int err; + if (addr == INADDR_LOOPBACK || addr == INADDR_ANY) { + ai->ai_canonname = get_my_canon_name(&err); + } else { + ai->ai_canonname = + get_canon_name_from_addr(ip,&err); + } + if (ai->ai_canonname == NULL) { + freeaddrinfo(ai); + return err; + } + } + + *res = ai; + return 0; +} + +/* + * get address info for multiple ipv4 addresses. + * + * Bugs: - servname can only be a number, not text. + */ + +static int getaddr_info_name(const char *node, + const char *service, + const struct addrinfo *hints, + struct addrinfo **res) +{ + struct addrinfo *listp = NULL, *prevp = NULL; + char **pptr = NULL; + int err; + struct hostent *hp = NULL; + unsigned short port = 0; + + if (service) { + port = (unsigned short)atoi(service); + } + + hp = gethostbyname(node); + err = check_hostent_err(hp); + if (err) { + return err; + } + + for(pptr = hp->h_addr_list; *pptr; pptr++) { + struct in_addr ip = *(struct in_addr *)*pptr; + struct addrinfo *ai = alloc_entry(hints, ip, port); + + if (!ai) { + freeaddrinfo(listp); + return EAI_MEMORY; + } + + if (!listp) { + listp = ai; + prevp = ai; + ai->ai_canonname = SMB_STRDUP(hp->h_name); + if (!ai->ai_canonname) { + freeaddrinfo(listp); + return EAI_MEMORY; + } + } else { + prevp->ai_next = ai; + prevp = ai; + } + } + *res = listp; + return 0; +} + +/* + * get address info for ipv4 sockets. + * + * Bugs: - servname can only be a number, not text. + */ + +int getaddrinfo(const char *node, + const char *service, + const struct addrinfo * hintp, + struct addrinfo ** res) +{ + struct addrinfo hints; + + /* Setup the hints struct. */ + if (hintp == NULL) { + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + } else { + memcpy(&hints, hintp, sizeof(hints)); + } + + if (hints.ai_family != AF_INET && hints.ai_family != AF_UNSPEC) { + return EAI_FAMILY; + } + + if (hints.ai_socktype == 0) { + hints.ai_socktype = SOCK_STREAM; + } + + if (!node && !service) { + return EAI_NONAME; + } + + if (node) { + if (node[0] == '\0') { + return getaddr_info_single_addr(service, + INADDR_ANY, + &hints, + res); + } else if (hints.ai_flags & AI_NUMERICHOST) { + struct in_addr ip; + if (inet_pton(AF_INET, node, &ip) <= 0) + return EAI_FAIL; + return getaddr_info_single_addr(service, + ntohl(ip.s_addr), + &hints, + res); + } else { + return getaddr_info_name(node, + service, + &hints, + res); + } + } else if (hints.ai_flags & AI_PASSIVE) { + return getaddr_info_single_addr(service, + INADDR_ANY, + &hints, + res); + } + return getaddr_info_single_addr(service, + INADDR_LOOPBACK, + &hints, + res); +} + + +void freeaddrinfo(struct addrinfo *res) +{ + struct addrinfo *next = NULL; + + for (;res; res = next) { + next = res->ai_next; + if (res->ai_canonname) { + free(res->ai_canonname); + } + if (res->ai_addr) { + free(res->ai_addr); + } + free(res); + } +} + + +const char *gai_strerror(int errcode) +{ +#ifdef HAVE_HSTRERROR + int hcode; + + switch (errcode) + { + case EAI_NONAME: + hcode = HOST_NOT_FOUND; + break; + case EAI_AGAIN: + hcode = TRY_AGAIN; + break; + case EAI_FAIL: + default: + hcode = NO_RECOVERY; + break; + } + + return hstrerror(hcode); +#else /* !HAVE_HSTRERROR */ + + switch (errcode) + { + case EAI_NONAME: + return "Unknown host"; + case EAI_AGAIN: + return "Host name lookup failure"; +#ifdef EAI_BADFLAGS + case EAI_BADFLAGS: + return "Invalid argument"; +#endif +#ifdef EAI_FAMILY + case EAI_FAMILY: + return "Address family not supported"; +#endif +#ifdef EAI_MEMORY + case EAI_MEMORY: + return "Not enough memory"; +#endif +#ifdef EAI_NODATA + case EAI_NODATA: + return "No host data of that type was found"; +#endif +#ifdef EAI_SERVICE + case EAI_SERVICE: + return "Class type not found"; +#endif +#ifdef EAI_SOCKTYPE + case EAI_SOCKTYPE: + return "Socket type not supported"; +#endif + default: + return "Unknown server error"; + } +#endif /* HAVE_HSTRERROR */ +} + +static int gethostnameinfo(const struct sockaddr *sa, + char *node, + size_t nodelen, + int flags) +{ + int ret = -1; + char *p = NULL; + + if (!(flags & NI_NUMERICHOST)) { + struct hostent *hp = gethostbyaddr( + (void *)&((struct sockaddr_in *)sa)->sin_addr, + sizeof (struct in_addr), + sa->sa_family); + ret = check_hostent_err(hp); + if (ret == 0) { + /* Name looked up successfully. */ + ret = snprintf(node, nodelen, "%s", hp->h_name); + if (ret < 0 || (size_t)ret >= nodelen) { + return EAI_MEMORY; + } + if (flags & NI_NOFQDN) { + p = strchr(node,'.'); + if (p) { + *p = '\0'; + } + } + return 0; + } + + if (flags & NI_NAMEREQD) { + /* If we require a name and didn't get one, + * automatically fail. */ + return ret; + } + /* Otherwise just fall into the numeric host code... */ + } + p = inet_ntoa(((struct sockaddr_in *)sa)->sin_addr); + ret = snprintf(node, nodelen, "%s", p); + if (ret < 0 || (size_t)ret >= nodelen) { + return EAI_MEMORY; + } + return 0; +} + +static int getservicenameinfo(const struct sockaddr *sa, + char *service, + size_t servicelen, + int flags) +{ + int ret = -1; + int port = ntohs(((struct sockaddr_in *)sa)->sin_port); + + if (!(flags & NI_NUMERICSERV)) { + struct servent *se = getservbyport( + port, + (flags & NI_DGRAM) ? "udp" : "tcp"); + if (se && se->s_name) { + /* Service name looked up successfully. */ + ret = snprintf(service, servicelen, "%s", se->s_name); + if (ret < 0 || (size_t)ret >= servicelen) { + return EAI_MEMORY; + } + return 0; + } + /* Otherwise just fall into the numeric service code... */ + } + ret = snprintf(service, servicelen, "%d", port); + if (ret < 0 || (size_t)ret >= servicelen) { + return EAI_MEMORY; + } + return 0; +} + +/* + * Convert an ipv4 address to a hostname. + * + * Bugs: - No IPv6 support. + */ +int getnameinfo(const struct sockaddr *sa, socklen_t salen, + char *node, size_t nodelen, + char *service, size_t servicelen, int flags) +{ + + /* Invalid arguments. */ + if (sa == NULL || (node == NULL && service == NULL)) { + return EAI_FAIL; + } + + if (sa->sa_family != AF_INET) { + return EAI_FAIL; + } + + if (salen < (socklen_t)sizeof (struct sockaddr_in)) { + return EAI_FAIL; + } + + if (node) { + int ret = gethostnameinfo(sa, node, nodelen, flags); + if (ret) + return ret; + } + + if (service) { + return getservicenameinfo(sa, service, servicelen, flags); + } + return 0; +} diff --git a/rsync/lib/getpass.c b/rsync/lib/getpass.c new file mode 100644 index 0000000..dea3126 --- /dev/null +++ b/rsync/lib/getpass.c @@ -0,0 +1,72 @@ +/* + * An implementation of getpass for systems that lack one. + * + * Copyright (C) 2013 Roman Donchenko + * + * 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, visit the http://fsf.org website. + */ + +#include +#include +#include + +#include "rsync.h" + +char *getpass(const char *prompt) +{ + static char password[256]; + + BOOL tty_changed = False, read_success; + struct termios tty_old, tty_new; + FILE *in = stdin, *out = stderr; + FILE *tty = fopen("/dev/tty", "w+"); + + if (tty) + in = out = tty; + + if (tcgetattr(fileno(in), &tty_old) == 0) { + tty_new = tty_old; + tty_new.c_lflag &= ~(ECHO | ISIG); + + if (tcsetattr(fileno(in), TCSAFLUSH, &tty_new) == 0) + tty_changed = True; + } + + if (!tty_changed) + fputs("(WARNING: will be visible) ", out); + fputs(prompt, out); + fflush(out); + + read_success = fgets(password, sizeof password, in) != NULL; + + /* Print the newline that hasn't been echoed. */ + fputc('\n', out); + + if (tty_changed) + tcsetattr(fileno(in), TCSAFLUSH, &tty_old); + + if (tty) + fclose(tty); + + if (read_success) { + /* Remove the trailing newline. */ + size_t password_len = strlen(password); + if (password_len && password[password_len - 1] == '\n') + password[password_len - 1] = '\0'; + + return password; + } + + return NULL; +} diff --git a/rsync/lib/inet_ntop.c b/rsync/lib/inet_ntop.c new file mode 100644 index 0000000..7be7368 --- /dev/null +++ b/rsync/lib/inet_ntop.c @@ -0,0 +1,186 @@ +/* + * Copyright (C) 1996-2001 Internet Software Consortium. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * INTERNET SOFTWARE CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING + * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION + * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + + +#include "rsync.h" + +#define NS_INT16SZ 2 +#define NS_IN6ADDRSZ 16 + +/* + * WARNING: Don't even consider trying to compile this on a system where + * sizeof(int) < 4. sizeof(int) > 4 is fine; all the world's not a VAX. + */ + +static const char *inet_ntop4(const unsigned char *src, char *dst, + size_t size); + +#ifdef AF_INET6 +static const char *inet_ntop6(const unsigned char *src, char *dst, + size_t size); +#endif + +/* char * + * isc_net_ntop(af, src, dst, size) + * convert a network format address to presentation format. + * return: + * pointer to presentation format address (`dst'), or NULL (see errno). + * author: + * Paul Vixie, 1996. + */ +const char * +inet_ntop(int af, const void *src, char *dst, size_t size) +{ + switch (af) { + case AF_INET: + return (inet_ntop4(src, dst, size)); +#ifdef AF_INET6 + case AF_INET6: + return (inet_ntop6(src, dst, size)); +#endif + default: + errno = EAFNOSUPPORT; + return (NULL); + } + /* NOTREACHED */ +} + +/* const char * + * inet_ntop4(src, dst, size) + * format an IPv4 address + * return: + * `dst' (as a const) + * notes: + * (1) uses no statics + * (2) takes a unsigned char* not an in_addr as input + * author: + * Paul Vixie, 1996. + */ +static const char * +inet_ntop4(const unsigned char *src, char *dst, size_t size) +{ + static const char *fmt = "%u.%u.%u.%u"; + char tmp[sizeof "255.255.255.255"]; + size_t len; + + len = snprintf(tmp, sizeof tmp, fmt, src[0], src[1], src[2], src[3]); + if (len >= size) { + errno = ENOSPC; + return (NULL); + } + memcpy(dst, tmp, len + 1); + + return (dst); +} + +/* const char * + * isc_inet_ntop6(src, dst, size) + * convert IPv6 binary address into presentation (printable) format + * author: + * Paul Vixie, 1996. + */ +#ifdef AF_INET6 +static const char * +inet_ntop6(const unsigned char *src, char *dst, size_t size) +{ + /* + * Note that int32_t and int16_t need only be "at least" large enough + * to contain a value of the specified size. On some systems, like + * Crays, there is no such thing as an integer variable with 16 bits. + * Keep this in mind if you think this function should have been coded + * to use pointer overlays. All the world's not a VAX. + */ + char tmp[sizeof "ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255"], *tp; + struct { int base, len; } best, cur; + unsigned int words[NS_IN6ADDRSZ / NS_INT16SZ]; + int i, inc; + + /* + * Preprocess: + * Copy the input (bytewise) array into a wordwise array. + * Find the longest run of 0x00's in src[] for :: shorthanding. + */ + memset(words, '\0', sizeof words); + for (i = 0; i < NS_IN6ADDRSZ; i++) + words[i / 2] |= (src[i] << ((1 - (i % 2)) << 3)); + best.base = -1; + cur.base = -1; + for (i = 0; i < (NS_IN6ADDRSZ / NS_INT16SZ); i++) { + if (words[i] == 0) { + if (cur.base == -1) + cur.base = i, cur.len = 1; + else + cur.len++; + } else { + if (cur.base != -1) { + if (best.base == -1 || cur.len > best.len) + best = cur; + cur.base = -1; + } + } + } + if (cur.base != -1) { + if (best.base == -1 || cur.len > best.len) + best = cur; + } + if (best.base != -1 && best.len < 2) + best.base = -1; + + /* + * Format the result. + */ + tp = tmp; + for (i = 0; i < (NS_IN6ADDRSZ / NS_INT16SZ); i++) { + /* Are we inside the best run of 0x00's? */ + if (best.base != -1 && i >= best.base && + i < (best.base + best.len)) { + if (i == best.base) + *tp++ = ':'; + continue; + } + /* Are we following an initial run of 0x00s or any real hex? */ + if (i != 0) + *tp++ = ':'; + /* Is this address an encapsulated IPv4? */ + if (i == 6 && best.base == 0 && + (best.len == 6 || (best.len == 5 && words[5] == 0xffff))) { + if (!inet_ntop4(src+12, tp, sizeof tmp - (tp - tmp))) + return (NULL); + tp += strlen(tp); + break; + } + inc = snprintf(tp, 5, "%x", words[i]); + assert(inc < 5); + tp += inc; + } + /* Was it a trailing run of 0x00's? */ + if (best.base != -1 && (best.base + best.len) == + (NS_IN6ADDRSZ / NS_INT16SZ)) + *tp++ = ':'; + *tp++ = '\0'; + + /* + * Check for overflow, copy, and we're done. + */ + if ((size_t)(tp - tmp) > size) { + errno = ENOSPC; + return (NULL); + } + memcpy(dst, tmp, tp - tmp); + return (dst); +} +#endif /* AF_INET6 */ diff --git a/rsync/lib/inet_pton.c b/rsync/lib/inet_pton.c new file mode 100644 index 0000000..4a0be88 --- /dev/null +++ b/rsync/lib/inet_pton.c @@ -0,0 +1,212 @@ +/* + * Copyright (C) 1996-2001 Internet Software Consortium. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * INTERNET SOFTWARE CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING + * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION + * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "rsync.h" + +#define NS_INT16SZ 2 +#define NS_INADDRSZ 4 +#define NS_IN6ADDRSZ 16 + +/* + * WARNING: Don't even consider trying to compile this on a system where + * sizeof(int) < 4. sizeof(int) > 4 is fine; all the world's not a VAX. + */ + +static int inet_pton4(const char *src, unsigned char *dst); +#ifdef INET6 +static int inet_pton6(const char *src, unsigned char *dst); +#endif + +/* int + * inet_pton(af, src, dst) + * convert from presentation format (which usually means ASCII printable) + * to network format (which is usually some kind of binary format). + * return: + * 1 if the address was valid for the specified address family + * 0 if the address wasn't valid (`dst' is untouched in this case) + * -1 if some other error occurred (`dst' is untouched in this case, too) + * author: + * Paul Vixie, 1996. + */ +int +inet_pton(int af, + const char *src, + void *dst) +{ + switch (af) { + case AF_INET: + return (inet_pton4(src, dst)); +#ifdef INET6 + case AF_INET6: + return (inet_pton6(src, dst)); +#endif + default: + errno = EAFNOSUPPORT; + return (-1); + } + /* NOTREACHED */ +} + +/* int + * inet_pton4(src, dst) + * like inet_aton() but without all the hexadecimal and shorthand. + * return: + * 1 if `src' is a valid dotted quad, else 0. + * notice: + * does not touch `dst' unless it's returning 1. + * author: + * Paul Vixie, 1996. + */ +static int +inet_pton4(src, dst) + const char *src; + unsigned char *dst; +{ + static const char digits[] = "0123456789"; + int saw_digit, octets, ch; + unsigned char tmp[NS_INADDRSZ], *tp; + + saw_digit = 0; + octets = 0; + *(tp = tmp) = 0; + while ((ch = *src++) != '\0') { + const char *pch; + + if ((pch = strchr(digits, ch)) != NULL) { + unsigned int new = *tp * 10 + (pch - digits); + + if (new > 255) + return (0); + *tp = new; + if (! saw_digit) { + if (++octets > 4) + return (0); + saw_digit = 1; + } + } else if (ch == '.' && saw_digit) { + if (octets == 4) + return (0); + *++tp = 0; + saw_digit = 0; + } else + return (0); + } + if (octets < 4) + return (0); + memcpy(dst, tmp, NS_INADDRSZ); + return (1); +} + +/* int + * inet_pton6(src, dst) + * convert presentation level address to network order binary form. + * return: + * 1 if `src' is a valid [RFC1884 2.2] address, else 0. + * notice: + * (1) does not touch `dst' unless it's returning 1. + * (2) :: in a full address is silently ignored. + * credit: + * inspired by Mark Andrews. + * author: + * Paul Vixie, 1996. + */ +#ifdef INET6 +static int +inet_pton6(src, dst) + const char *src; + unsigned char *dst; +{ + static const char xdigits_l[] = "0123456789abcdef", + xdigits_u[] = "0123456789ABCDEF"; + unsigned char tmp[NS_IN6ADDRSZ], *tp, *endp, *colonp; + const char *xdigits, *curtok; + int ch, saw_xdigit; + unsigned int val; + + memset((tp = tmp), '\0', NS_IN6ADDRSZ); + endp = tp + NS_IN6ADDRSZ; + colonp = NULL; + /* Leading :: requires some special handling. */ + if (*src == ':') + if (*++src != ':') + return (0); + curtok = src; + saw_xdigit = 0; + val = 0; + while ((ch = *src++) != '\0') { + const char *pch; + + if ((pch = strchr((xdigits = xdigits_l), ch)) == NULL) + pch = strchr((xdigits = xdigits_u), ch); + if (pch != NULL) { + val <<= 4; + val |= (pch - xdigits); + if (val > 0xffff) + return (0); + saw_xdigit = 1; + continue; + } + if (ch == ':') { + curtok = src; + if (!saw_xdigit) { + if (colonp) + return (0); + colonp = tp; + continue; + } + if (tp + NS_INT16SZ > endp) + return (0); + *tp++ = (unsigned char) (val >> 8) & 0xff; + *tp++ = (unsigned char) val & 0xff; + saw_xdigit = 0; + val = 0; + continue; + } + if (ch == '.' && ((tp + NS_INADDRSZ) <= endp) && + inet_pton4(curtok, tp) > 0) { + tp += NS_INADDRSZ; + saw_xdigit = 0; + break; /* '\0' was seen by inet_pton4(). */ + } + return (0); + } + if (saw_xdigit) { + if (tp + NS_INT16SZ > endp) + return (0); + *tp++ = (unsigned char) (val >> 8) & 0xff; + *tp++ = (unsigned char) val & 0xff; + } + if (colonp != NULL) { + /* + * Since some memmove()'s erroneously fail to handle + * overlapping regions, we'll do the shift by hand. + */ + const int n = tp - colonp; + int i; + + for (i = 1; i <= n; i++) { + endp[- i] = colonp[n - i]; + colonp[n - i] = 0; + } + tp = endp; + } + if (tp != endp) + return (0); + memcpy(dst, tmp, NS_IN6ADDRSZ); + return (1); +} +#endif diff --git a/rsync/lib/md5.c b/rsync/lib/md5.c new file mode 100644 index 0000000..4baa963 --- /dev/null +++ b/rsync/lib/md5.c @@ -0,0 +1,304 @@ +/* + * RFC 1321 compliant MD5 implementation + * + * Copyright (C) 2001-2003 Christophe Devine + * + * 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, visit the http://fsf.org website. + */ + +#include "rsync.h" + +void md5_begin(md_context *ctx) +{ + ctx->A = 0x67452301; + ctx->B = 0xEFCDAB89; + ctx->C = 0x98BADCFE; + ctx->D = 0x10325476; + + ctx->totalN = ctx->totalN2 = 0; +} + +static void md5_process(md_context *ctx, const uchar data[CSUM_CHUNK]) +{ + uint32 X[16], A, B, C, D; + + A = ctx->A; + B = ctx->B; + C = ctx->C; + D = ctx->D; + + X[0] = IVALu(data, 0); + X[1] = IVALu(data, 4); + X[2] = IVALu(data, 8); + X[3] = IVALu(data, 12); + X[4] = IVALu(data, 16); + X[5] = IVALu(data, 20); + X[6] = IVALu(data, 24); + X[7] = IVALu(data, 28); + X[8] = IVALu(data, 32); + X[9] = IVALu(data, 36); + X[10] = IVALu(data, 40); + X[11] = IVALu(data, 44); + X[12] = IVALu(data, 48); + X[13] = IVALu(data, 52); + X[14] = IVALu(data, 56); + X[15] = IVALu(data, 60); + +#define S(x,n) ((x << n) | ((x & 0xFFFFFFFF) >> (32 - n))) + +#define P(a,b,c,d,k,s,t) a += F(b,c,d) + X[k] + t, a = S(a,s) + b + +#define F(x,y,z) (z ^ (x & (y ^ z))) + + P(A, B, C, D, 0, 7, 0xD76AA478); + P(D, A, B, C, 1, 12, 0xE8C7B756); + P(C, D, A, B, 2, 17, 0x242070DB); + P(B, C, D, A, 3, 22, 0xC1BDCEEE); + P(A, B, C, D, 4, 7, 0xF57C0FAF); + P(D, A, B, C, 5, 12, 0x4787C62A); + P(C, D, A, B, 6, 17, 0xA8304613); + P(B, C, D, A, 7, 22, 0xFD469501); + P(A, B, C, D, 8, 7, 0x698098D8); + P(D, A, B, C, 9, 12, 0x8B44F7AF); + P(C, D, A, B, 10, 17, 0xFFFF5BB1); + P(B, C, D, A, 11, 22, 0x895CD7BE); + P(A, B, C, D, 12, 7, 0x6B901122); + P(D, A, B, C, 13, 12, 0xFD987193); + P(C, D, A, B, 14, 17, 0xA679438E); + P(B, C, D, A, 15, 22, 0x49B40821); + +#undef F +#define F(x,y,z) (y ^ (z & (x ^ y))) + + P(A, B, C, D, 1, 5, 0xF61E2562); + P(D, A, B, C, 6, 9, 0xC040B340); + P(C, D, A, B, 11, 14, 0x265E5A51); + P(B, C, D, A, 0, 20, 0xE9B6C7AA); + P(A, B, C, D, 5, 5, 0xD62F105D); + P(D, A, B, C, 10, 9, 0x02441453); + P(C, D, A, B, 15, 14, 0xD8A1E681); + P(B, C, D, A, 4, 20, 0xE7D3FBC8); + P(A, B, C, D, 9, 5, 0x21E1CDE6); + P(D, A, B, C, 14, 9, 0xC33707D6); + P(C, D, A, B, 3, 14, 0xF4D50D87); + P(B, C, D, A, 8, 20, 0x455A14ED); + P(A, B, C, D, 13, 5, 0xA9E3E905); + P(D, A, B, C, 2, 9, 0xFCEFA3F8); + P(C, D, A, B, 7, 14, 0x676F02D9); + P(B, C, D, A, 12, 20, 0x8D2A4C8A); + +#undef F +#define F(x,y,z) (x ^ y ^ z) + + P(A, B, C, D, 5, 4, 0xFFFA3942); + P(D, A, B, C, 8, 11, 0x8771F681); + P(C, D, A, B, 11, 16, 0x6D9D6122); + P(B, C, D, A, 14, 23, 0xFDE5380C); + P(A, B, C, D, 1, 4, 0xA4BEEA44); + P(D, A, B, C, 4, 11, 0x4BDECFA9); + P(C, D, A, B, 7, 16, 0xF6BB4B60); + P(B, C, D, A, 10, 23, 0xBEBFBC70); + P(A, B, C, D, 13, 4, 0x289B7EC6); + P(D, A, B, C, 0, 11, 0xEAA127FA); + P(C, D, A, B, 3, 16, 0xD4EF3085); + P(B, C, D, A, 6, 23, 0x04881D05); + P(A, B, C, D, 9, 4, 0xD9D4D039); + P(D, A, B, C, 12, 11, 0xE6DB99E5); + P(C, D, A, B, 15, 16, 0x1FA27CF8); + P(B, C, D, A, 2, 23, 0xC4AC5665); + +#undef F +#define F(x,y,z) (y ^ (x | ~z)) + + P(A, B, C, D, 0, 6, 0xF4292244); + P(D, A, B, C, 7, 10, 0x432AFF97); + P(C, D, A, B, 14, 15, 0xAB9423A7); + P(B, C, D, A, 5, 21, 0xFC93A039); + P(A, B, C, D, 12, 6, 0x655B59C3); + P(D, A, B, C, 3, 10, 0x8F0CCC92); + P(C, D, A, B, 10, 15, 0xFFEFF47D); + P(B, C, D, A, 1, 21, 0x85845DD1); + P(A, B, C, D, 8, 6, 0x6FA87E4F); + P(D, A, B, C, 15, 10, 0xFE2CE6E0); + P(C, D, A, B, 6, 15, 0xA3014314); + P(B, C, D, A, 13, 21, 0x4E0811A1); + P(A, B, C, D, 4, 6, 0xF7537E82); + P(D, A, B, C, 11, 10, 0xBD3AF235); + P(C, D, A, B, 2, 15, 0x2AD7D2BB); + P(B, C, D, A, 9, 21, 0xEB86D391); + +#undef F + + ctx->A += A; + ctx->B += B; + ctx->C += C; + ctx->D += D; +} + +void md5_update(md_context *ctx, const uchar *input, uint32 length) +{ + uint32 left, fill; + + if (!length) + return; + + left = ctx->totalN & 0x3F; + fill = CSUM_CHUNK - left; + + ctx->totalN += length; + ctx->totalN &= 0xFFFFFFFF; + + if (ctx->totalN < length) + ctx->totalN2++; + + if (left && length >= fill) { + memcpy(ctx->buffer + left, input, fill); + md5_process(ctx, ctx->buffer); + length -= fill; + input += fill; + left = 0; + } + + while (length >= CSUM_CHUNK) { + md5_process(ctx, input); + length -= CSUM_CHUNK; + input += CSUM_CHUNK; + } + + if (length) + memcpy(ctx->buffer + left, input, length); +} + +static uchar md5_padding[CSUM_CHUNK] = { 0x80 }; + +void md5_result(md_context *ctx, uchar digest[MD5_DIGEST_LEN]) +{ + uint32 last, padn; + uint32 high, low; + uchar msglen[8]; + + high = (ctx->totalN >> 29) + | (ctx->totalN2 << 3); + low = (ctx->totalN << 3); + + SIVALu(msglen, 0, low); + SIVALu(msglen, 4, high); + + last = ctx->totalN & 0x3F; + padn = last < 56 ? 56 - last : 120 - last; + + md5_update(ctx, md5_padding, padn); + md5_update(ctx, msglen, 8); + + SIVALu(digest, 0, ctx->A); + SIVALu(digest, 4, ctx->B); + SIVALu(digest, 8, ctx->C); + SIVALu(digest, 12, ctx->D); +} + +void get_md5(uchar *out, const uchar *input, int n) +{ + md_context ctx; + md5_begin(&ctx); + md5_update(&ctx, input, n); + md5_result(&ctx, out); +} + +#ifdef TEST_MD5 + +#include +#include + +/* + * those are the standard RFC 1321 test vectors + */ + +static struct { + char *str, *md5; +} tests[] = { + { "", + "d41d8cd98f00b204e9800998ecf8427e" }, + { "a", + "0cc175b9c0f1b6a831c399e269772661" }, + { "abc", + "900150983cd24fb0d6963f7d28e17f72" }, + { "message digest", + "f96b697d7cb7938d525a2f31aaf161d0" }, + { "abcdefghijklmnopqrstuvwxyz", + "c3fcd3d76192e4007dfb496cca67e13b" }, + { "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", + "d174ab98d277d9f5a5611c2c9f419d9f" }, + { "12345678901234567890123456789012345678901234567890123456789012345678901234567890", + "57edf4a22be3c955ac49da2e2107b67a" }, + { NULL, NULL } +}; + +int main(int argc, char *argv[]) +{ + FILE *f; + int i, j; + char output[33]; + md_context ctx; + uchar buf[1000]; + uchar md5sum[MD5_DIGEST_LEN]; + + if (argc < 2) { + printf("\nMD5 Validation Tests:\n\n"); + + for (i = 0; tests[i].str; i++) { + char *str = tests[i].str; + char *chk = tests[i].md5; + + printf(" Test %d ", i + 1); + + get_md5(md5sum, str, strlen(str)); + + for (j = 0; j < MD5_DIGEST_LEN; j++) + sprintf(output + j * 2, "%02x", md5sum[j]); + + if (memcmp(output, chk, 32)) { + printf("failed!\n"); + return 1; + } + + printf("passed.\n"); + } + + printf("\n"); + return 0; + } + + while (--argc) { + if (!(f = fopen(*++argv, "rb"))) { + perror("fopen"); + return 1; + } + + md5_begin(&ctx); + + while ((i = fread(buf, 1, sizeof buf, f)) > 0) + md5_update(&ctx, buf, i); + + md5_result(&ctx, md5sum); + + for (j = 0; j < MD5_DIGEST_LEN; j++) + printf("%02x", md5sum[j]); + + printf(" %s\n", *argv); + } + + return 0; +} + +#endif diff --git a/rsync/lib/mdfour.c b/rsync/lib/mdfour.c new file mode 100644 index 0000000..16b2358 --- /dev/null +++ b/rsync/lib/mdfour.c @@ -0,0 +1,246 @@ +/* + * Unix SMB/Netbios implementation. + * Version 1.9. + * An implementation of MD4 designed for use in the SMB authentication protocol. + * + * Copyright (C) 1997-1998 Andrew Tridgell + * Copyright (C) 2005-2014 Wayne Davison + * + * 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, visit the http://fsf.org website. + */ + +#include "rsync.h" + +/* NOTE: This code makes no attempt to be fast! + * + * It assumes that a int is at least 32 bits long. */ + +static md_context *m; + +#define MASK32 (0xffffffff) + +#define F(X,Y,Z) ((((X)&(Y)) | ((~(X))&(Z)))) +#define G(X,Y,Z) ((((X)&(Y)) | ((X)&(Z)) | ((Y)&(Z)))) +#define H(X,Y,Z) (((X)^(Y)^(Z))) +#define lshift(x,s) (((((x)<<(s))&MASK32) | (((x)>>(32-(s)))&MASK32))) + +#define ROUND1(a,b,c,d,k,s) a = lshift((a + F(b,c,d) + M[k])&MASK32, s) +#define ROUND2(a,b,c,d,k,s) a = lshift((a + G(b,c,d) + M[k] + 0x5A827999)&MASK32,s) +#define ROUND3(a,b,c,d,k,s) a = lshift((a + H(b,c,d) + M[k] + 0x6ED9EBA1)&MASK32,s) + +/* this applies md4 to 64 byte chunks */ +static void mdfour64(uint32 *M) +{ + uint32 AA, BB, CC, DD; + uint32 A,B,C,D; + + A = m->A; B = m->B; C = m->C; D = m->D; + AA = A; BB = B; CC = C; DD = D; + + ROUND1(A,B,C,D, 0, 3); ROUND1(D,A,B,C, 1, 7); + ROUND1(C,D,A,B, 2, 11); ROUND1(B,C,D,A, 3, 19); + ROUND1(A,B,C,D, 4, 3); ROUND1(D,A,B,C, 5, 7); + ROUND1(C,D,A,B, 6, 11); ROUND1(B,C,D,A, 7, 19); + ROUND1(A,B,C,D, 8, 3); ROUND1(D,A,B,C, 9, 7); + ROUND1(C,D,A,B, 10, 11); ROUND1(B,C,D,A, 11, 19); + ROUND1(A,B,C,D, 12, 3); ROUND1(D,A,B,C, 13, 7); + ROUND1(C,D,A,B, 14, 11); ROUND1(B,C,D,A, 15, 19); + + ROUND2(A,B,C,D, 0, 3); ROUND2(D,A,B,C, 4, 5); + ROUND2(C,D,A,B, 8, 9); ROUND2(B,C,D,A, 12, 13); + ROUND2(A,B,C,D, 1, 3); ROUND2(D,A,B,C, 5, 5); + ROUND2(C,D,A,B, 9, 9); ROUND2(B,C,D,A, 13, 13); + ROUND2(A,B,C,D, 2, 3); ROUND2(D,A,B,C, 6, 5); + ROUND2(C,D,A,B, 10, 9); ROUND2(B,C,D,A, 14, 13); + ROUND2(A,B,C,D, 3, 3); ROUND2(D,A,B,C, 7, 5); + ROUND2(C,D,A,B, 11, 9); ROUND2(B,C,D,A, 15, 13); + + ROUND3(A,B,C,D, 0, 3); ROUND3(D,A,B,C, 8, 9); + ROUND3(C,D,A,B, 4, 11); ROUND3(B,C,D,A, 12, 15); + ROUND3(A,B,C,D, 2, 3); ROUND3(D,A,B,C, 10, 9); + ROUND3(C,D,A,B, 6, 11); ROUND3(B,C,D,A, 14, 15); + ROUND3(A,B,C,D, 1, 3); ROUND3(D,A,B,C, 9, 9); + ROUND3(C,D,A,B, 5, 11); ROUND3(B,C,D,A, 13, 15); + ROUND3(A,B,C,D, 3, 3); ROUND3(D,A,B,C, 11, 9); + ROUND3(C,D,A,B, 7, 11); ROUND3(B,C,D,A, 15, 15); + + A += AA; B += BB; + C += CC; D += DD; + + A &= MASK32; B &= MASK32; + C &= MASK32; D &= MASK32; + + m->A = A; m->B = B; m->C = C; m->D = D; +} + +static void copy64(uint32 *M, const uchar *in) +{ + int i; + + for (i = 0; i < MD4_DIGEST_LEN; i++) { + M[i] = (in[i*4+3] << 24) | (in[i*4+2] << 16) + | (in[i*4+1] << 8) | (in[i*4+0] << 0); + } +} + +static void copy4(uchar *out,uint32 x) +{ + out[0] = x&0xFF; + out[1] = (x>>8)&0xFF; + out[2] = (x>>16)&0xFF; + out[3] = (x>>24)&0xFF; +} + +void mdfour_begin(md_context *md) +{ + md->A = 0x67452301; + md->B = 0xefcdab89; + md->C = 0x98badcfe; + md->D = 0x10325476; + md->totalN = 0; + md->totalN2 = 0; +} + +static void mdfour_tail(const uchar *in, uint32 length) +{ + uchar buf[128]; + uint32 M[16]; + extern int protocol_version; + + /* + * Count total number of bits, modulo 2^64 + */ + m->totalN += length << 3; + if (m->totalN < (length << 3)) + m->totalN2++; + m->totalN2 += length >> 29; + + memset(buf, 0, 128); + if (length) + memcpy(buf, in, length); + buf[length] = 0x80; + + if (length <= 55) { + copy4(buf+56, m->totalN); + /* + * Prior to protocol version 27 only the number of bits + * modulo 2^32 was included. MD4 requires the number + * of bits modulo 2^64, which was fixed starting with + * protocol version 27. + */ + if (protocol_version >= 27) + copy4(buf+60, m->totalN2); + copy64(M, buf); + mdfour64(M); + } else { + copy4(buf+120, m->totalN); + /* + * Prior to protocol version 27 only the number of bits + * modulo 2^32 was included. MD4 requires the number + * of bits modulo 2^64, which was fixed starting with + * protocol version 27. + */ + if (protocol_version >= 27) + copy4(buf+124, m->totalN2); + copy64(M, buf); + mdfour64(M); + copy64(M, buf+64); + mdfour64(M); + } +} + +void mdfour_update(md_context *md, const uchar *in, uint32 length) +{ + uint32 M[16]; + + m = md; + + if (length == 0) + mdfour_tail(in, length); + + while (length >= 64) { + copy64(M, in); + mdfour64(M); + in += 64; + length -= 64; + m->totalN += 64 << 3; + if (m->totalN < 64 << 3) + m->totalN2++; + } + + if (length) + mdfour_tail(in, length); +} + +void mdfour_result(md_context *md, uchar digest[MD4_DIGEST_LEN]) +{ + m = md; + + copy4(digest, m->A); + copy4(digest+4, m->B); + copy4(digest+8, m->C); + copy4(digest+12, m->D); +} + +void mdfour(uchar digest[MD4_DIGEST_LEN], uchar *in, int length) +{ + md_context md; + mdfour_begin(&md); + mdfour_update(&md, in, length); + mdfour_result(&md, digest); +} + +#ifdef TEST_MDFOUR +int protocol_version = 28; + +static void file_checksum1(char *fname) +{ + int fd, i, was_multiple_of_64 = 1; + md_context md; + uchar buf[64*1024], sum[MD4_DIGEST_LEN]; + + fd = open(fname,O_RDONLY); + if (fd == -1) { + perror("fname"); + exit(1); + } + + mdfour_begin(&md); + + while (1) { + int n = read(fd, buf, sizeof buf); + if (n <= 0) + break; + was_multiple_of_64 = !(n % 64); + mdfour_update(&md, buf, n); + } + if (was_multiple_of_64 && protocol_version >= 27) + mdfour_update(&md, buf, 0); + + close(fd); + + mdfour_result(&md, sum); + + for (i = 0; i < MD4_DIGEST_LEN; i++) + printf("%02X", sum[i]); + printf("\n"); +} + + int main(int argc, char *argv[]) +{ + while (--argc) + file_checksum1(*++argv); + return 0; +} +#endif diff --git a/rsync/lib/mdigest.h b/rsync/lib/mdigest.h new file mode 100644 index 0000000..e0e33ed --- /dev/null +++ b/rsync/lib/mdigest.h @@ -0,0 +1,26 @@ +/* The include file for both the MD4 and MD5 routines. */ + +#define MD4_DIGEST_LEN 16 +#define MD5_DIGEST_LEN 16 +#define MAX_DIGEST_LEN MD5_DIGEST_LEN + +#define CSUM_CHUNK 64 + +typedef struct { + uint32 A, B, C, D; + uint32 totalN; /* bit count, lower 32 bits */ + uint32 totalN2; /* bit count, upper 32 bits */ + uchar buffer[CSUM_CHUNK]; +} md_context; + +void mdfour_begin(md_context *md); +void mdfour_update(md_context *md, const uchar *in, uint32 length); +void mdfour_result(md_context *md, uchar digest[MD4_DIGEST_LEN]); + +void get_mdfour(uchar digest[MD4_DIGEST_LEN], const uchar *in, int length); + +void md5_begin(md_context *ctx); +void md5_update(md_context *ctx, const uchar *input, uint32 length); +void md5_result(md_context *ctx, uchar digest[MD5_DIGEST_LEN]); + +void get_md5(uchar digest[MD5_DIGEST_LEN], const uchar *input, int n); diff --git a/rsync/lib/permstring.c b/rsync/lib/permstring.c new file mode 100644 index 0000000..7b2414d --- /dev/null +++ b/rsync/lib/permstring.c @@ -0,0 +1,65 @@ +/* + * A single utility routine. + * + * Copyright (C) 1996 Andrew Tridgell + * Copyright (C) 1996 Paul Mackerras + * Copyright (C) 2001 Martin Pool + * Copyright (C) 2003-2014 Wayne Davison + * + * 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, visit the http://fsf.org website. + */ + +#include "rsync.h" + +/* Produce a string representation of Unix mode bits like that used by ls(1). + * The "buf" buffer must be at least 11 characters. */ +void permstring(char *perms, mode_t mode) +{ + static const char *perm_map = "rwxrwxrwx"; + int i; + + strlcpy(perms, "----------", 11); + + for (i = 0; i < 9; i++) { + if (mode & (1 << i)) + perms[9-i] = perm_map[8-i]; + } + + /* Handle setuid/sticky bits. You might think the indices are + * off by one, but remember there's a type char at the + * start. */ + if (mode & S_ISUID) + perms[3] = (mode & S_IXUSR) ? 's' : 'S'; + + if (mode & S_ISGID) + perms[6] = (mode & S_IXGRP) ? 's' : 'S'; + +#ifdef S_ISVTX + if (mode & S_ISVTX) + perms[9] = (mode & S_IXOTH) ? 't' : 'T'; +#endif + + if (S_ISDIR(mode)) + perms[0] = 'd'; + else if (S_ISLNK(mode)) + perms[0] = 'l'; + else if (S_ISBLK(mode)) + perms[0] = 'b'; + else if (S_ISCHR(mode)) + perms[0] = 'c'; + else if (S_ISSOCK(mode)) + perms[0] = 's'; + else if (S_ISFIFO(mode)) + perms[0] = 'p'; +} diff --git a/rsync/lib/permstring.h b/rsync/lib/permstring.h new file mode 100644 index 0000000..b996f2a --- /dev/null +++ b/rsync/lib/permstring.h @@ -0,0 +1,3 @@ +#define PERMSTRING_SIZE 11 + +void permstring(char *perms, mode_t mode); diff --git a/rsync/lib/pool_alloc.3 b/rsync/lib/pool_alloc.3 new file mode 100644 index 0000000..6c22b92 --- /dev/null +++ b/rsync/lib/pool_alloc.3 @@ -0,0 +1,268 @@ +.ds d \-\^\- +.ds o \fR[\fP +.ds c \fR]\fP +.ds | \fR|\fP +.de D +\\.B \*d\\$1 +.. +.de DI +\\.BI \*d\\$1 \\$2 +.. +.de DR +\\.BR \*d\\$1 \\$2 +.. +.de Di +\\.BI \*d\\$1 " \\$2" +.. +.de Db +\\.B \*d\\$1 " \\$2" +.. +.de Df +\\.B \*d\*ono\*c\\$1 +.. +.de See +See \fB\\$1\fP for details. +.. +.de SeeIn +See \fB\\$1\fP in \fB\\$2\fP for details. +.. +.TH POOL_ALLOC 3 +.SH NAME +pool_alloc, pool_free, pool_free_old, pool_talloc, pool_tfree, pool_create, pool_destroy, pool_boundary +\- Allocate and free memory in managed allocation pools. +.SH SYNOPSIS +.B #include "pool_alloc.h" + +\fBstruct alloc_pool *pool_create(size_t \fIsize\fB, size_t \fIquantum\fB, void (*\fIbomb\fB)(char *), int \fIflags\fB); + +\fBvoid pool_destroy(struct alloc_pool *\fIpool\fB); + +\fBvoid *pool_alloc(struct alloc_pool *\fIpool\fB, size_t \fIsize\fB, char *\fImsg\fB); + +\fBvoid pool_free(struct alloc_pool *\fIpool\fB, size_t \fIsize\fB, void *\fIaddr\fB); + +\fBvoid pool_free_old(struct alloc_pool *\fIpool\fB, void *\fIaddr\fB); + +\fBvoid *pool_talloc(struct alloc_pool *\fIpool\fB, \fItype\fB), int \fIcount\fB, char *\fImsg\fB); + +\fBvoid pool_tfree(struct alloc_pool *\fIpool\fB, \fItype\fB, int \fIcount\fB, void *\fIaddr\fB); + +\fBvoid pool_boundary(struct alloc_pool *\fIpool\fB, sise_t \fIsize\fB); +.SH DESCRIPTION +.P +The pool allocation routines use +.B malloc() +for underlying memory management. +What allocation pools do is cause memory within a given pool +to be allocated in large contiguous blocks +(called extents) that will be reusable when freed. Unlike +.BR malloc() , +the allocations are not managed individually. +Instead, each extent tracks the total free memory within the +extent. Each extent can either be used to allocate memory +or to manage the freeing of memory within that extent. +When an extent has less free memory than a given +allocation request, the current extent ceases to be used +for allocation. See also the +.B pool_boundary() +function. +.P +This form of memory management is suited to large numbers of small +related allocations that are held for a while +and then freed as a group. +Because the +underlying allocations are done in large contiguous extents, +when an extent is freed, it can release a large enough +contiguous block of memory to allow the memory to be returned +to the OS for use by whatever program needs it. +You can allocate from one or more memory pools and/or +.B malloc() +all at the same time without interfering with how pools work. +.P +.B pool_create() +Creates an allocation pool for subsequent calls to the pool +allocation functions. +When an extent is created for allocations it will be +.I size +bytes. +Allocations from the pool have their sizes rounded up to a +multiple of +.I quantum +bytes in length. +Specifying +.B 0 +for +.I quantum +will produce a quantum that should meet maximal alignment +on most platforms. +Unless +.B POOL_NO_QALIGN +is set in the +.IR flags , +allocations will be aligned to addresses that are a +multiple of +.IR quantum . +A +.B NULL +may be specified for the +.I bomb +function pointer if it is not needed. (See the +.B pool_alloc() +function for how it is used.) +If +.B POOL_CLEAR +is set in the +.IR flags , +all allocations from the pool will be initialized to zeros. +If either +.B POOL_PREPEND +or +.B POOL_INTERN +is specified in the +.IR flags , +each extent's data structure will be allocated at the start of the +.IR size -length +buffer (rather than as a separate, non-pool allocation), with the +former extending the +.I size +to hold the structure, and the latter subtracting the structure's +length from the indicated +.IR size . +.P +.B pool_destroy() +destroys an allocation +.I pool +and frees all its associated memory. +.P +.B pool_alloc() +allocates +.I size +bytes from the specified +.IR pool . +If +.I size +is +.BR 0 , +.I quantum +bytes will be allocated. +If the pool has been created without +.BR POOL_NO_QALIGN , +every chunk of memory that is returned will be suitably aligned. +You can use this with the default +.I quantum +size to ensure that all memory can store a variable of any type. +If the requested memory cannot be allocated, the +.I bomb() +function will be called with +.I msg +as its sole argument (if the function was defined at the time +the pool was created), and then a +.B NULL +address is returned (assuming that the bomb function didn't exit). +.P +.B pool_free() +frees +.I size +bytes pointed to by an +.I addr +that was previously allocated in the specified +.IR pool . +If +.I size +is +.BR 0 , +.I quantum +bytes will be freed. +The memory freed within an extent will not be reusable until +all of the memory in that extent has been freed with one +exception: the most recent pool allocation may be freed back +into the pool prior to making any further allocations. +If enough free calls are made to indicate that an extent has no +remaining allocated objects (as computed by the total freed size for +an extent), its memory will be completely freed back to the system. +If +.I addr +is +.BR NULL , +no memory will be freed, but subsequent allocations will come +from a new extent. +.P +.B pool_free_old() +takes a boundary +.I addr +value that was returned by +.B pool_boundary() +and frees up any extents in the +.I pool +that have data allocated from that point backward in time. +NOTE: you must NOT mix calls to both +.B pool_free +and +.B pool_free_old +on the same pool! +.P +.B pool_boundary() +asks for a boundary value that can be sent to +.B pool_free_old() +at a later time to free up all memory allocated prior to a particular +moment in time. +If the extent that holds the boundary point has allocations from after the +boundary point, it will not be freed until a future +.B pool_free_old() +call encompasses the entirety of the extent's data. +If +.I len +is non-zero, the call will also check if the active extent has at least +that much free memory available in it, and if not, it will mark the +extent as inactive, forcing a new extent to be used for future allocations. +(You can specify -1 for +.I len +if you want to force a new extent to start.) +.P +.B pool_talloc() +is a macro that takes a +.I type +and a +.I count +instead of a +.IR size . +It casts the return value to the correct pointer type. +.P +.B pool_tfree +is a macro that calls +.B pool_free +on memory that was allocated by +.BR pool_talloc() . +.SH RETURN VALUE +.B pool_create() +returns a pointer to +.BR "struct alloc_pool" . +.P +.B pool_alloc() +and +.B pool_talloc() +return pointers to the allocated memory, +or NULL if the request fails. +The return type of +.B pool_alloc() +will normally require casting to the desired type but +.B pool_talloc() +will returns a pointer of the requested +.IR type . +.P +.B pool_boundary() +returns a pointer that should only be used in a call to +.BR pool_free_old() . +.P +.BR pool_free() , +.BR pool_free_old() , +.B pool_tfree() +and +.B pool_destroy() +return no value. +.SH SEE ALSO +.nf +malloc(3) +.SH AUTHOR +pool_alloc was created by J.W. Schultz of Pegasystems Technologies. +.SH BUGS AND ISSUES diff --git a/rsync/lib/pool_alloc.c b/rsync/lib/pool_alloc.c new file mode 100644 index 0000000..5856d59 --- /dev/null +++ b/rsync/lib/pool_alloc.c @@ -0,0 +1,376 @@ +#include "rsync.h" + +#define POOL_DEF_EXTENT (32 * 1024) + +#define POOL_QALIGN_P2 (1<<16) /* power-of-2 qalign */ + +struct alloc_pool +{ + size_t size; /* extent size */ + size_t quantum; /* allocation quantum */ + struct pool_extent *extents; /* top extent is "live" */ + void (*bomb)(); /* function to call if + * malloc fails */ + int flags; + + /* statistical data */ + unsigned long e_created; /* extents created */ + unsigned long e_freed; /* extents destroyed */ + int64 n_allocated; /* calls to alloc */ + int64 n_freed; /* calls to free */ + int64 b_allocated; /* cum. bytes allocated */ + int64 b_freed; /* cum. bytes freed */ +}; + +struct pool_extent +{ + struct pool_extent *next; + void *start; /* starting address */ + size_t free; /* free bytecount */ + size_t bound; /* trapped free bytes */ +}; + +struct align_test { + uchar foo; + union { + int64 i; + void *p; + } bar; +}; + +#define MINALIGN offsetof(struct align_test, bar) + +/* Temporarily cast a void* var into a char* var when adding an offset (to + * keep some compilers from complaining about the pointer arithmetic). */ +#define PTR_ADD(b,o) ( (void*) ((char*)(b) + (o)) ) + +alloc_pool_t +pool_create(size_t size, size_t quantum, void (*bomb)(const char *), int flags) +{ + struct alloc_pool *pool; + + if (!(pool = new0(struct alloc_pool))) + return NULL; + + if ((MINALIGN & (MINALIGN - 1)) != 0) { + if (bomb) + (*bomb)("Compiler error: MINALIGN is not a power of 2\n"); + return NULL; + } + + if (!size) + size = POOL_DEF_EXTENT; + if (!quantum) + quantum = MINALIGN; + + if (flags & POOL_INTERN) { + if (size <= sizeof (struct pool_extent)) + size = quantum; + else + size -= sizeof (struct pool_extent); + flags |= POOL_PREPEND; + } + + if (quantum <= 1) + flags = (flags | POOL_NO_QALIGN) & ~POOL_QALIGN_P2; + else if (!(flags & POOL_NO_QALIGN)) { + if (size % quantum) + size += quantum - size % quantum; + /* If quantum is a power of 2, we'll avoid using modulus. */ + if (!(quantum & (quantum - 1))) + flags |= POOL_QALIGN_P2; + } + + pool->size = size; + pool->quantum = quantum; + pool->bomb = bomb; + pool->flags = flags; + + return pool; +} + +void +pool_destroy(alloc_pool_t p) +{ + struct alloc_pool *pool = (struct alloc_pool *) p; + struct pool_extent *cur, *next; + + if (!pool) + return; + + for (cur = pool->extents; cur; cur = next) { + next = cur->next; + if (pool->flags & POOL_PREPEND) + free(PTR_ADD(cur->start, -sizeof (struct pool_extent))); + else { + free(cur->start); + free(cur); + } + } + + free(pool); +} + +void * +pool_alloc(alloc_pool_t p, size_t len, const char *bomb_msg) +{ + struct alloc_pool *pool = (struct alloc_pool *) p; + if (!pool) + return NULL; + + if (!len) + len = pool->quantum; + else if (pool->flags & POOL_QALIGN_P2) { + if (len & (pool->quantum - 1)) + len += pool->quantum - (len & (pool->quantum - 1)); + } else if (!(pool->flags & POOL_NO_QALIGN)) { + if (len % pool->quantum) + len += pool->quantum - len % pool->quantum; + } + + if (len > pool->size) + goto bomb_out; + + if (!pool->extents || len > pool->extents->free) { + void *start; + size_t asize; + struct pool_extent *ext; + + asize = pool->size; + if (pool->flags & POOL_PREPEND) + asize += sizeof (struct pool_extent); + + if (!(start = new_array(char, asize))) + goto bomb_out; + + if (pool->flags & POOL_CLEAR) + memset(start, 0, asize); + + if (pool->flags & POOL_PREPEND) { + ext = start; + start = PTR_ADD(start, sizeof (struct pool_extent)); + } else if (!(ext = new(struct pool_extent))) + goto bomb_out; + ext->start = start; + ext->free = pool->size; + ext->bound = 0; + ext->next = pool->extents; + pool->extents = ext; + + pool->e_created++; + } + + pool->n_allocated++; + pool->b_allocated += len; + + pool->extents->free -= len; + + return PTR_ADD(pool->extents->start, pool->extents->free); + + bomb_out: + if (pool->bomb) + (*pool->bomb)(bomb_msg); + return NULL; +} + +/* This function allows you to declare memory in the pool that you are done + * using. If you free all the memory in a pool's extent, that extent will + * be freed. */ +void +pool_free(alloc_pool_t p, size_t len, void *addr) +{ + struct alloc_pool *pool = (struct alloc_pool *)p; + struct pool_extent *cur, *prev; + + if (!pool) + return; + + if (!addr) { + /* A NULL addr starts a fresh extent for new allocations. */ + if ((cur = pool->extents) != NULL && cur->free != pool->size) { + cur->bound += cur->free; + cur->free = 0; + } + return; + } + + if (!len) + len = pool->quantum; + else if (pool->flags & POOL_QALIGN_P2) { + if (len & (pool->quantum - 1)) + len += pool->quantum - (len & (pool->quantum - 1)); + } else if (!(pool->flags & POOL_NO_QALIGN)) { + if (len % pool->quantum) + len += pool->quantum - len % pool->quantum; + } + + pool->n_freed++; + pool->b_freed += len; + + for (prev = NULL, cur = pool->extents; cur; prev = cur, cur = cur->next) { + if (addr >= cur->start + && addr < PTR_ADD(cur->start, pool->size)) + break; + } + if (!cur) + return; + + if (!prev) { + /* The "live" extent is kept ready for more allocations. */ + if (cur->free + cur->bound + len >= pool->size) { + if (pool->flags & POOL_CLEAR) { + memset(PTR_ADD(cur->start, cur->free), 0, + pool->size - cur->free); + } + cur->free = pool->size; + cur->bound = 0; + } else if (addr == PTR_ADD(cur->start, cur->free)) { + if (pool->flags & POOL_CLEAR) + memset(addr, 0, len); + cur->free += len; + } else + cur->bound += len; + } else { + cur->bound += len; + + if (cur->free + cur->bound >= pool->size) { + prev->next = cur->next; + if (pool->flags & POOL_PREPEND) + free(PTR_ADD(cur->start, -sizeof (struct pool_extent))); + else { + free(cur->start); + free(cur); + } + pool->e_freed++; + } else if (prev != pool->extents) { + /* Move the extent to be the first non-live extent. */ + prev->next = cur->next; + cur->next = pool->extents->next; + pool->extents->next = cur; + } + } +} + +/* This allows you to declare that the given address marks the edge of some + * pool memory that is no longer needed. Any extents that hold only data + * older than the boundary address are freed. NOTE: You MUST NOT USE BOTH + * pool_free() and pool_free_old() on the same pool!! */ +void +pool_free_old(alloc_pool_t p, void *addr) +{ + struct alloc_pool *pool = (struct alloc_pool *)p; + struct pool_extent *cur, *prev, *next; + + if (!pool || !addr) + return; + + for (prev = NULL, cur = pool->extents; cur; prev = cur, cur = cur->next) { + if (addr >= cur->start + && addr < PTR_ADD(cur->start, pool->size)) + break; + } + if (!cur) + return; + + if (addr == PTR_ADD(cur->start, cur->free)) { + if (prev) { + prev->next = NULL; + next = cur; + } else { + /* The most recent live extent can just be reset. */ + if (pool->flags & POOL_CLEAR) + memset(addr, 0, pool->size - cur->free); + cur->free = pool->size; + cur->bound = 0; + next = cur->next; + cur->next = NULL; + } + } else { + next = cur->next; + cur->next = NULL; + } + + while ((cur = next) != NULL) { + next = cur->next; + if (pool->flags & POOL_PREPEND) + free(PTR_ADD(cur->start, -sizeof (struct pool_extent))); + else { + free(cur->start); + free(cur); + } + pool->e_freed++; + } +} + +/* If the current extent doesn't have "len" free space in it, mark it as full + * so that the next alloc will start a new extent. If len is (size_t)-1, this + * bump will always occur. The function returns a boundary address that can + * be used with pool_free_old(), or a NULL if no memory is allocated. */ +void * +pool_boundary(alloc_pool_t p, size_t len) +{ + struct alloc_pool *pool = (struct alloc_pool *)p; + struct pool_extent *cur; + + if (!pool || !pool->extents) + return NULL; + + cur = pool->extents; + + if (cur->free < len) { + cur->bound += cur->free; + cur->free = 0; + } + + return PTR_ADD(cur->start, cur->free); +} + +#define FDPRINT(label, value) \ + do { \ + int len = snprintf(buf, sizeof buf, label, value); \ + if (write(fd, buf, len) != len) \ + ret = -1; \ + } while (0) + +#define FDEXTSTAT(ext) \ + do { \ + int len = snprintf(buf, sizeof buf, " %12ld %5ld\n", \ + (long)ext->free, (long)ext->bound); \ + if (write(fd, buf, len) != len) \ + ret = -1; \ + } while (0) + +int +pool_stats(alloc_pool_t p, int fd, int summarize) +{ + struct alloc_pool *pool = (struct alloc_pool *) p; + struct pool_extent *cur; + char buf[BUFSIZ]; + int ret = 0; + + if (!pool) + return ret; + + FDPRINT(" Extent size: %12ld\n", (long) pool->size); + FDPRINT(" Alloc quantum: %12ld\n", (long) pool->quantum); + FDPRINT(" Extents created: %12ld\n", pool->e_created); + FDPRINT(" Extents freed: %12ld\n", pool->e_freed); + FDPRINT(" Alloc count: %12.0f\n", (double) pool->n_allocated); + FDPRINT(" Free Count: %12.0f\n", (double) pool->n_freed); + FDPRINT(" Bytes allocated: %12.0f\n", (double) pool->b_allocated); + FDPRINT(" Bytes freed: %12.0f\n", (double) pool->b_freed); + + if (summarize) + return ret; + + if (!pool->extents) + return ret; + + if (write(fd, "\n", 1) != 1) + ret = -1; + + for (cur = pool->extents; cur; cur = cur->next) + FDEXTSTAT(cur); + + return ret; +} diff --git a/rsync/lib/pool_alloc.h b/rsync/lib/pool_alloc.h new file mode 100644 index 0000000..c7368a7 --- /dev/null +++ b/rsync/lib/pool_alloc.h @@ -0,0 +1,21 @@ +#include + +#define POOL_CLEAR (1<<0) /* zero fill allocations */ +#define POOL_NO_QALIGN (1<<1) /* don't align data to quanta */ +#define POOL_INTERN (1<<2) /* Allocate extent structures */ +#define POOL_PREPEND (1<<3) /* or prepend to extent data */ + +typedef void *alloc_pool_t; + +alloc_pool_t pool_create(size_t size, size_t quantum, void (*bomb)(const char *), int flags); +void pool_destroy(alloc_pool_t pool); +void *pool_alloc(alloc_pool_t pool, size_t size, const char *bomb_msg); +void pool_free(alloc_pool_t pool, size_t size, void *addr); +void pool_free_old(alloc_pool_t pool, void *addr); +void *pool_boundary(alloc_pool_t pool, size_t size); + +#define pool_talloc(pool, type, count, bomb_msg) \ + ((type *)pool_alloc(pool, sizeof(type) * count, bomb_msg)) + +#define pool_tfree(pool, type, count, addr) \ + (pool_free(pool, sizeof(type) * count, addr)) diff --git a/rsync/lib/snprintf.c b/rsync/lib/snprintf.c new file mode 100644 index 0000000..c17868f --- /dev/null +++ b/rsync/lib/snprintf.c @@ -0,0 +1,1512 @@ +/* + * NOTE: If you change this file, please merge it into rsync, samba, etc. + */ + +/* + * Copyright Patrick Powell 1995 + * This code is based on code written by Patrick Powell (papowell@astart.com) + * It may be used for any purpose as long as this notice remains intact + * on all source code distributions + */ + +/************************************************************** + * Original: + * Patrick Powell Tue Apr 11 09:48:21 PDT 1995 + * A bombproof version of doprnt (dopr) included. + * Sigh. This sort of thing is always nasty do deal with. Note that + * the version here does not include floating point... + * + * snprintf() is used instead of sprintf() as it does limit checks + * for string length. This covers a nasty loophole. + * + * The other functions are there to prevent NULL pointers from + * causing nast effects. + * + * More Recently: + * Brandon Long 9/15/96 for mutt 0.43 + * This was ugly. It is still ugly. I opted out of floating point + * numbers, but the formatter understands just about everything + * from the normal C string format, at least as far as I can tell from + * the Solaris 2.5 printf(3S) man page. + * + * Brandon Long 10/22/97 for mutt 0.87.1 + * Ok, added some minimal floating point support, which means this + * probably requires libm on most operating systems. Don't yet + * support the exponent (e,E) and sigfig (g,G). Also, fmtint() + * was pretty badly broken, it just wasn't being exercised in ways + * which showed it, so that's been fixed. Also, formated the code + * to mutt conventions, and removed dead code left over from the + * original. Also, there is now a builtin-test, just compile with: + * gcc -I.. -DTEST_SNPRINTF -o snprintf snprintf.c -lm + * and run snprintf for results. + * + * Thomas Roessler 01/27/98 for mutt 0.89i + * The PGP code was using unsigned hexadecimal formats. + * Unfortunately, unsigned formats simply didn't work. + * + * Michael Elkins 03/05/98 for mutt 0.90.8 + * The original code assumed that both snprintf() and vsnprintf() were + * missing. Some systems only have snprintf() but not vsnprintf(), so + * the code is now broken down under HAVE_SNPRINTF and HAVE_VSNPRINTF. + * + * Andrew Tridgell (tridge@samba.org) Oct 1998 + * fixed handling of %.0f + * added test for HAVE_LONG_DOUBLE + * + * tridge@samba.org, idra@samba.org, April 2001 + * got rid of fcvt code (twas buggy and made testing harder) + * added C99 semantics + * + * date: 2002/12/19 19:56:31; author: herb; state: Exp; lines: +2 -0 + * actually print args for %g and %e + * + * date: 2002/06/03 13:37:52; author: jmcd; state: Exp; lines: +8 -0 + * Since includes.h isn't included here, VA_COPY has to be defined here. I don't + * see any include file that is guaranteed to be here, so I'm defining it + * locally. Fixes AIX and Solaris builds. + * + * date: 2002/06/03 03:07:24; author: tridge; state: Exp; lines: +5 -13 + * put the ifdef for HAVE_VA_COPY in one place rather than in lots of + * functions + * + * date: 2002/05/17 14:51:22; author: jmcd; state: Exp; lines: +21 -4 + * Fix usage of va_list passed as an arg. Use __va_copy before using it + * when it exists. + * + * date: 2002/04/16 22:38:04; author: idra; state: Exp; lines: +20 -14 + * Fix incorrect zpadlen handling in fmtfp. + * Thanks to Ollie Oldham for spotting it. + * few mods to make it easier to compile the tests. + * addedd the "Ollie" test to the floating point ones. + * + * Martin Pool (mbp@samba.org) April 2003 + * Remove NO_CONFIG_H so that the test case can be built within a source + * tree with less trouble. + * Remove unnecessary SAFE_FREE() definition. + * + * Martin Pool (mbp@samba.org) May 2003 + * Put in a prototype for dummy_snprintf() to quiet compiler warnings. + * + * Move #endif to make sure VA_COPY, LDOUBLE, etc are defined even + * if the C library has some snprintf functions already. + * + * Darren Tucker (dtucker@zip.com.au) 2005 + * Fix bug allowing read overruns of the source string with "%.*s" + * Usually harmless unless the read runs outside the process' allocation + * (eg if your malloc does guard pages) in which case it will segfault. + * From OpenSSH. Also added test for same. + * + * Simo Sorce (idra@samba.org) Jan 2006 + * + * Add support for position independent parameters + * fix fmtstr now it conforms to sprintf wrt min.max + * + **************************************************************/ + +#include "config.h" + +#ifdef TEST_SNPRINTF /* need math library headers for testing */ + +/* In test mode, we pretend that this system doesn't have any snprintf + * functions, regardless of what config.h says. */ +# undef HAVE_SNPRINTF +# undef HAVE_VSNPRINTF +# undef HAVE_C99_VSNPRINTF +# undef HAVE_ASPRINTF +# undef HAVE_VASPRINTF +# include +#endif /* TEST_SNPRINTF */ + +#ifdef HAVE_STRING_H +#include +#endif + +#ifdef HAVE_STRINGS_H +#include +#endif +#ifdef HAVE_CTYPE_H +#include +#endif +#include +#include +#ifdef HAVE_STDLIB_H +#include +#endif + +#if defined(HAVE_SNPRINTF) && defined(HAVE_VSNPRINTF) && defined(HAVE_C99_VSNPRINTF) +/* only include stdio.h if we are not re-defining snprintf or vsnprintf */ +#include + /* make the compiler happy with an empty file */ + void dummy_snprintf(void); + void dummy_snprintf(void) {} +#endif /* HAVE_SNPRINTF, etc */ + +#ifdef STDC_HEADERS +#include +#endif + +#ifdef HAVE_LONG_DOUBLE +#define LDOUBLE long double +#else +#define LDOUBLE double +#endif + +#if !defined HAVE_LONG_LONG && SIZEOF_LONG_LONG +#define HAVE_LONG_LONG 1 +#endif +#ifdef HAVE_LONG_LONG +#define LLONG long long +#else +#define LLONG long +#endif + +#ifndef VA_COPY +#if defined HAVE_VA_COPY || defined va_copy +#define VA_COPY(dest, src) va_copy(dest, src) +#else +#ifdef HAVE___VA_COPY +#define VA_COPY(dest, src) __va_copy(dest, src) +#else +#define VA_COPY(dest, src) (dest) = (src) +#endif +#endif +#endif + +/* yes this really must be a ||. Don't muck with this (tridge) */ +#if !defined(HAVE_VSNPRINTF) || !defined(HAVE_C99_VSNPRINTF) + +/* + * dopr(): poor man's version of doprintf + */ + +/* format read states */ +#define DP_S_DEFAULT 0 +#define DP_S_FLAGS 1 +#define DP_S_MIN 2 +#define DP_S_DOT 3 +#define DP_S_MAX 4 +#define DP_S_MOD 5 +#define DP_S_CONV 6 +#define DP_S_DONE 7 + +/* format flags - Bits */ +#define DP_F_MINUS (1 << 0) +#define DP_F_PLUS (1 << 1) +#define DP_F_SPACE (1 << 2) +#define DP_F_NUM (1 << 3) +#define DP_F_ZERO (1 << 4) +#define DP_F_UP (1 << 5) +#define DP_F_UNSIGNED (1 << 6) + +/* Conversion Flags */ +#define DP_C_CHAR 1 +#define DP_C_SHORT 2 +#define DP_C_LONG 3 +#define DP_C_LDOUBLE 4 +#define DP_C_LLONG 5 +#define DP_C_SIZET 6 + +/* Chunk types */ +#define CNK_FMT_STR 0 +#define CNK_INT 1 +#define CNK_OCTAL 2 +#define CNK_UINT 3 +#define CNK_HEX 4 +#define CNK_FLOAT 5 +#define CNK_CHAR 6 +#define CNK_STRING 7 +#define CNK_PTR 8 +#define CNK_NUM 9 +#define CNK_PRCNT 10 + +#define char_to_int(p) ((p)- '0') +#ifndef MAX +#define MAX(p,q) (((p) >= (q)) ? (p) : (q)) +#endif + +struct pr_chunk { + int type; /* chunk type */ + int num; /* parameter number */ + int min; + int max; + int flags; + int cflags; + int start; + int len; + LLONG value; + LDOUBLE fvalue; + char *strvalue; + void *pnum; + struct pr_chunk *min_star; + struct pr_chunk *max_star; + struct pr_chunk *next; +}; + +struct pr_chunk_x { + struct pr_chunk **chunks; + int num; +}; + +static int dopr(char *buffer, size_t maxlen, const char *format, + va_list args_in); +static void fmtstr(char *buffer, size_t *currlen, size_t maxlen, + char *value, int flags, int min, int max); +static void fmtint(char *buffer, size_t *currlen, size_t maxlen, + LLONG value, int base, int min, int max, int flags); +static void fmtfp(char *buffer, size_t *currlen, size_t maxlen, + LDOUBLE fvalue, int min, int max, int flags); +static void dopr_outch(char *buffer, size_t *currlen, size_t maxlen, char c); +static struct pr_chunk *new_chunk(void); +static int add_cnk_list_entry(struct pr_chunk_x **list, + int max_num, struct pr_chunk *chunk); + +static int dopr(char *buffer, size_t maxlen, const char *format, va_list args_in) +{ + char ch; + int state; + int pflag; + int pnum; + int pfirst; + size_t currlen; + va_list args; + const char *base; + struct pr_chunk *chunks = NULL; + struct pr_chunk *cnk = NULL; + struct pr_chunk_x *clist = NULL; + int max_pos; + int ret = -1; + + VA_COPY(args, args_in); + + state = DP_S_DEFAULT; + pfirst = 1; + pflag = 0; + pnum = 0; + + max_pos = 0; + base = format; + ch = *format++; + + /* retrieve the string structure as chunks */ + while (state != DP_S_DONE) { + if (ch == '\0') + state = DP_S_DONE; + + switch(state) { + case DP_S_DEFAULT: + + if (cnk) { + cnk->next = new_chunk(); + cnk = cnk->next; + } else { + cnk = new_chunk(); + } + if (!cnk) goto done; + if (!chunks) chunks = cnk; + + if (ch == '%') { + state = DP_S_FLAGS; + ch = *format++; + } else { + cnk->type = CNK_FMT_STR; + cnk->start = format - base -1; + while ((ch != '\0') && (ch != '%')) ch = *format++; + cnk->len = format - base - cnk->start -1; + } + break; + case DP_S_FLAGS: + switch (ch) { + case '-': + cnk->flags |= DP_F_MINUS; + ch = *format++; + break; + case '+': + cnk->flags |= DP_F_PLUS; + ch = *format++; + break; + case ' ': + cnk->flags |= DP_F_SPACE; + ch = *format++; + break; + case '#': + cnk->flags |= DP_F_NUM; + ch = *format++; + break; + case '0': + cnk->flags |= DP_F_ZERO; + ch = *format++; + break; + case 'I': + /* internationalization not supported yet */ + ch = *format++; + break; + default: + state = DP_S_MIN; + break; + } + break; + case DP_S_MIN: + if (isdigit((unsigned char)ch)) { + cnk->min = 10 * cnk->min + char_to_int (ch); + ch = *format++; + } else if (ch == '$') { + if (!pfirst && !pflag) { + /* parameters must be all positioned or none */ + goto done; + } + if (pfirst) { + pfirst = 0; + pflag = 1; + } + if (cnk->min == 0) /* what ?? */ + goto done; + cnk->num = cnk->min; + cnk->min = 0; + ch = *format++; + } else if (ch == '*') { + if (pfirst) pfirst = 0; + cnk->min_star = new_chunk(); + if (!cnk->min_star) /* out of memory :-( */ + goto done; + cnk->min_star->type = CNK_INT; + if (pflag) { + int num; + ch = *format++; + if (!isdigit((unsigned char)ch)) { + /* parameters must be all positioned or none */ + goto done; + } + for (num = 0; isdigit((unsigned char)ch); ch = *format++) { + num = 10 * num + char_to_int(ch); + } + cnk->min_star->num = num; + if (ch != '$') /* what ?? */ + goto done; + } else { + cnk->min_star->num = ++pnum; + } + max_pos = add_cnk_list_entry(&clist, max_pos, cnk->min_star); + if (max_pos == 0) /* out of memory :-( */ + goto done; + ch = *format++; + state = DP_S_DOT; + } else { + if (pfirst) pfirst = 0; + state = DP_S_DOT; + } + break; + case DP_S_DOT: + if (ch == '.') { + state = DP_S_MAX; + ch = *format++; + } else { + state = DP_S_MOD; + } + break; + case DP_S_MAX: + if (isdigit((unsigned char)ch)) { + if (cnk->max < 0) + cnk->max = 0; + cnk->max = 10 * cnk->max + char_to_int (ch); + ch = *format++; + } else if (ch == '$') { + if (!pfirst && !pflag) { + /* parameters must be all positioned or none */ + goto done; + } + if (cnk->max <= 0) /* what ?? */ + goto done; + cnk->num = cnk->max; + cnk->max = -1; + ch = *format++; + } else if (ch == '*') { + cnk->max_star = new_chunk(); + if (!cnk->max_star) /* out of memory :-( */ + goto done; + cnk->max_star->type = CNK_INT; + if (pflag) { + int num; + ch = *format++; + if (!isdigit((unsigned char)ch)) { + /* parameters must be all positioned or none */ + goto done; + } + for (num = 0; isdigit((unsigned char)ch); ch = *format++) { + num = 10 * num + char_to_int(ch); + } + cnk->max_star->num = num; + if (ch != '$') /* what ?? */ + goto done; + } else { + cnk->max_star->num = ++pnum; + } + max_pos = add_cnk_list_entry(&clist, max_pos, cnk->max_star); + if (max_pos == 0) /* out of memory :-( */ + goto done; + + ch = *format++; + state = DP_S_MOD; + } else { + state = DP_S_MOD; + } + break; + case DP_S_MOD: + switch (ch) { + case 'h': + cnk->cflags = DP_C_SHORT; + ch = *format++; + if (ch == 'h') { + cnk->cflags = DP_C_CHAR; + ch = *format++; + } + break; + case 'l': + cnk->cflags = DP_C_LONG; + ch = *format++; + if (ch == 'l') { /* It's a long long */ + cnk->cflags = DP_C_LLONG; + ch = *format++; + } + break; + case 'L': + cnk->cflags = DP_C_LDOUBLE; + ch = *format++; + break; + case 'z': + cnk->cflags = DP_C_SIZET; + ch = *format++; + break; + default: + break; + } + state = DP_S_CONV; + break; + case DP_S_CONV: + if (cnk->num == 0) cnk->num = ++pnum; + max_pos = add_cnk_list_entry(&clist, max_pos, cnk); + if (max_pos == 0) /* out of memory :-( */ + goto done; + + switch (ch) { + case 'd': + case 'i': + cnk->type = CNK_INT; + break; + case 'o': + cnk->type = CNK_OCTAL; + cnk->flags |= DP_F_UNSIGNED; + break; + case 'u': + cnk->type = CNK_UINT; + cnk->flags |= DP_F_UNSIGNED; + break; + case 'X': + cnk->flags |= DP_F_UP; + case 'x': + cnk->type = CNK_HEX; + cnk->flags |= DP_F_UNSIGNED; + break; + case 'A': + /* hex float not supported yet */ + case 'E': + case 'G': + case 'F': + cnk->flags |= DP_F_UP; + case 'a': + /* hex float not supported yet */ + case 'e': + case 'f': + case 'g': + cnk->type = CNK_FLOAT; + break; + case 'c': + cnk->type = CNK_CHAR; + break; + case 's': + cnk->type = CNK_STRING; + break; + case 'p': + cnk->type = CNK_PTR; + cnk->flags |= DP_F_UNSIGNED; + break; + case 'n': + cnk->type = CNK_NUM; + break; + case '%': + cnk->type = CNK_PRCNT; + break; + default: + /* Unknown, bail out*/ + goto done; + } + ch = *format++; + state = DP_S_DEFAULT; + break; + case DP_S_DONE: + break; + default: + /* hmm? */ + break; /* some picky compilers need this */ + } + } + + /* retrieve the format arguments */ + for (pnum = 0; pnum < max_pos; pnum++) { + int i; + + if (clist[pnum].num == 0) { + /* ignoring a parameter should not be permitted + * all parameters must be matched at least once + * BUT seem some system ignore this rule ... + * at least my glibc based system does --SSS + */ +#ifdef DEBUG_SNPRINTF + printf("parameter at position %d not used\n", pnum+1); +#endif + /* eat the parameter */ + va_arg (args, int); + continue; + } + for (i = 1; i < clist[pnum].num; i++) { + if (clist[pnum].chunks[0]->type != clist[pnum].chunks[i]->type) { + /* nooo noo no! + * all the references to a parameter + * must be of the same type + */ + goto done; + } + } + cnk = clist[pnum].chunks[0]; + switch (cnk->type) { + case CNK_INT: + if (cnk->cflags == DP_C_SHORT) + cnk->value = va_arg (args, int); + else if (cnk->cflags == DP_C_LONG) + cnk->value = va_arg (args, long int); + else if (cnk->cflags == DP_C_LLONG) + cnk->value = va_arg (args, LLONG); + else if (cnk->cflags == DP_C_SIZET) + cnk->value = va_arg (args, ssize_t); + else + cnk->value = va_arg (args, int); + + for (i = 1; i < clist[pnum].num; i++) { + clist[pnum].chunks[i]->value = cnk->value; + } + break; + + case CNK_OCTAL: + case CNK_UINT: + case CNK_HEX: + if (cnk->cflags == DP_C_SHORT) + cnk->value = va_arg (args, unsigned int); + else if (cnk->cflags == DP_C_LONG) + cnk->value = (unsigned long int)va_arg (args, unsigned long int); + else if (cnk->cflags == DP_C_LLONG) + cnk->value = (LLONG)va_arg (args, unsigned LLONG); + else if (cnk->cflags == DP_C_SIZET) + cnk->value = (size_t)va_arg (args, size_t); + else + cnk->value = (unsigned int)va_arg (args, unsigned int); + + for (i = 1; i < clist[pnum].num; i++) { + clist[pnum].chunks[i]->value = cnk->value; + } + break; + + case CNK_FLOAT: + if (cnk->cflags == DP_C_LDOUBLE) + cnk->fvalue = va_arg (args, LDOUBLE); + else + cnk->fvalue = va_arg (args, double); + + for (i = 1; i < clist[pnum].num; i++) { + clist[pnum].chunks[i]->fvalue = cnk->fvalue; + } + break; + + case CNK_CHAR: + cnk->value = va_arg (args, int); + + for (i = 1; i < clist[pnum].num; i++) { + clist[pnum].chunks[i]->value = cnk->value; + } + break; + + case CNK_STRING: + cnk->strvalue = va_arg (args, char *); + if (!cnk->strvalue) cnk->strvalue = "(NULL)"; + + for (i = 1; i < clist[pnum].num; i++) { + clist[pnum].chunks[i]->strvalue = cnk->strvalue; + } + break; + + case CNK_PTR: + cnk->strvalue = va_arg (args, void *); + for (i = 1; i < clist[pnum].num; i++) { + clist[pnum].chunks[i]->strvalue = cnk->strvalue; + } + break; + + case CNK_NUM: + if (cnk->cflags == DP_C_CHAR) + cnk->pnum = va_arg (args, char *); + else if (cnk->cflags == DP_C_SHORT) + cnk->pnum = va_arg (args, short int *); + else if (cnk->cflags == DP_C_LONG) + cnk->pnum = va_arg (args, long int *); + else if (cnk->cflags == DP_C_LLONG) + cnk->pnum = va_arg (args, LLONG *); + else if (cnk->cflags == DP_C_SIZET) + cnk->pnum = va_arg (args, ssize_t *); + else + cnk->pnum = va_arg (args, int *); + + for (i = 1; i < clist[pnum].num; i++) { + clist[pnum].chunks[i]->pnum = cnk->pnum; + } + break; + + case CNK_PRCNT: + break; + + default: + /* what ?? */ + goto done; + } + } + /* print out the actual string from chunks */ + currlen = 0; + cnk = chunks; + while (cnk) { + int len, min, max; + + if (cnk->min_star) min = cnk->min_star->value; + else min = cnk->min; + if (cnk->max_star) max = cnk->max_star->value; + else max = cnk->max; + + switch (cnk->type) { + + case CNK_FMT_STR: + if (maxlen != 0 && maxlen > currlen) { + if (maxlen > (currlen + cnk->len)) len = cnk->len; + else len = maxlen - currlen; + + memcpy(&(buffer[currlen]), &(base[cnk->start]), len); + } + currlen += cnk->len; + + break; + + case CNK_INT: + case CNK_UINT: + fmtint (buffer, &currlen, maxlen, cnk->value, 10, min, max, cnk->flags); + break; + + case CNK_OCTAL: + fmtint (buffer, &currlen, maxlen, cnk->value, 8, min, max, cnk->flags); + break; + + case CNK_HEX: + fmtint (buffer, &currlen, maxlen, cnk->value, 16, min, max, cnk->flags); + break; + + case CNK_FLOAT: + fmtfp (buffer, &currlen, maxlen, cnk->fvalue, min, max, cnk->flags); + break; + + case CNK_CHAR: + dopr_outch (buffer, &currlen, maxlen, cnk->value); + break; + + case CNK_STRING: + if (max == -1) { + max = strlen(cnk->strvalue); + } + fmtstr (buffer, &currlen, maxlen, cnk->strvalue, cnk->flags, min, max); + break; + + case CNK_PTR: + fmtint (buffer, &currlen, maxlen, (long)(cnk->strvalue), 16, min, max, cnk->flags); + break; + + case CNK_NUM: + if (cnk->cflags == DP_C_CHAR) + *((char *)(cnk->pnum)) = (char)currlen; + else if (cnk->cflags == DP_C_SHORT) + *((short int *)(cnk->pnum)) = (short int)currlen; + else if (cnk->cflags == DP_C_LONG) + *((long int *)(cnk->pnum)) = (long int)currlen; + else if (cnk->cflags == DP_C_LLONG) + *((LLONG *)(cnk->pnum)) = (LLONG)currlen; + else if (cnk->cflags == DP_C_SIZET) + *((ssize_t *)(cnk->pnum)) = (ssize_t)currlen; + else + *((int *)(cnk->pnum)) = (int)currlen; + break; + + case CNK_PRCNT: + dopr_outch (buffer, &currlen, maxlen, '%'); + break; + + default: + /* what ?? */ + goto done; + } + cnk = cnk->next; + } + if (maxlen != 0) { + if (currlen < maxlen - 1) + buffer[currlen] = '\0'; + else if (maxlen > 0) + buffer[maxlen - 1] = '\0'; + } + ret = currlen; + +done: + va_end(args); + + while (chunks) { + cnk = chunks->next; + free(chunks); + chunks = cnk; + } + if (clist) { + for (pnum = 0; pnum < max_pos; pnum++) { + if (clist[pnum].chunks) free(clist[pnum].chunks); + } + free(clist); + } + return ret; +} + +static void fmtstr(char *buffer, size_t *currlen, size_t maxlen, + char *value, int flags, int min, int max) +{ + int padlen, strln; /* amount to pad */ + int cnt = 0; + +#ifdef DEBUG_SNPRINTF + printf("fmtstr min=%d max=%d s=[%s]\n", min, max, value); +#endif + if (value == 0) { + value = ""; + } + + for (strln = 0; strln < max && value[strln]; ++strln); /* strlen */ + padlen = min - strln; + if (padlen < 0) + padlen = 0; + if (flags & DP_F_MINUS) + padlen = -padlen; /* Left Justify */ + + while (padlen > 0) { + dopr_outch (buffer, currlen, maxlen, ' '); + --padlen; + } + while (*value && (cnt < max)) { + dopr_outch (buffer, currlen, maxlen, *value++); + ++cnt; + } + while (padlen < 0) { + dopr_outch (buffer, currlen, maxlen, ' '); + ++padlen; + } +} + +/* Have to handle DP_F_NUM (ie 0x and 0 alternates) */ + +static void fmtint(char *buffer, size_t *currlen, size_t maxlen, + LLONG value, int base, int min, int max, int flags) +{ + int signvalue = 0; + unsigned LLONG uvalue; + char convert[20]; + int place = 0; + int spadlen = 0; /* amount to space pad */ + int zpadlen = 0; /* amount to zero pad */ + int caps = 0; + + if (max < 0) + max = 0; + + uvalue = value; + + if(!(flags & DP_F_UNSIGNED)) { + if( value < 0 ) { + signvalue = '-'; + uvalue = -value; + } else { + if (flags & DP_F_PLUS) /* Do a sign (+/i) */ + signvalue = '+'; + else if (flags & DP_F_SPACE) + signvalue = ' '; + } + } + + if (flags & DP_F_UP) caps = 1; /* Should characters be upper case? */ + + do { + convert[place++] = + (caps? "0123456789ABCDEF":"0123456789abcdef") + [uvalue % (unsigned)base ]; + uvalue = (uvalue / (unsigned)base ); + } while(uvalue && (place < 20)); + if (place == 20) place--; + convert[place] = 0; + + zpadlen = max - place; + spadlen = min - MAX (max, place) - (signvalue ? 1 : 0); + if (zpadlen < 0) zpadlen = 0; + if (spadlen < 0) spadlen = 0; + if (flags & DP_F_ZERO) { + zpadlen = MAX(zpadlen, spadlen); + spadlen = 0; + } + if (flags & DP_F_MINUS) + spadlen = -spadlen; /* Left Justifty */ + +#ifdef DEBUG_SNPRINTF + printf("zpad: %d, spad: %d, min: %d, max: %d, place: %d\n", + zpadlen, spadlen, min, max, place); +#endif + + /* Spaces */ + while (spadlen > 0) { + dopr_outch (buffer, currlen, maxlen, ' '); + --spadlen; + } + + /* Sign */ + if (signvalue) + dopr_outch (buffer, currlen, maxlen, signvalue); + + /* Zeros */ + if (zpadlen > 0) { + while (zpadlen > 0) { + dopr_outch (buffer, currlen, maxlen, '0'); + --zpadlen; + } + } + + /* Digits */ + while (place > 0) + dopr_outch (buffer, currlen, maxlen, convert[--place]); + + /* Left Justified spaces */ + while (spadlen < 0) { + dopr_outch (buffer, currlen, maxlen, ' '); + ++spadlen; + } +} + +static LDOUBLE abs_val(LDOUBLE value) +{ + LDOUBLE result = value; + + if (value < 0) + result = -value; + + return result; +} + +static LDOUBLE POW10(int exp) +{ + LDOUBLE result = 1; + + while (exp) { + result *= 10; + exp--; + } + + return result; +} + +static LLONG ROUND(LDOUBLE value) +{ + LLONG intpart; + + intpart = (LLONG)value; + value = value - intpart; + if (value >= 0.5) intpart++; + + return intpart; +} + +/* a replacement for modf that doesn't need the math library. Should + be portable, but slow */ +static double my_modf(double x0, double *iptr) +{ + int i; + LLONG l=0; + double x = x0; + double f = 1.0; + + for (i=0;i<100;i++) { + l = (long)x; + if (l <= (x+1) && l >= (x-1)) break; + x *= 0.1; + f *= 10.0; + } + + if (i == 100) { + /* yikes! the number is beyond what we can handle. What do we do? */ + (*iptr) = 0; + return 0; + } + + if (i != 0) { + double i2; + double ret; + + ret = my_modf(x0-l*f, &i2); + (*iptr) = l*f + i2; + return ret; + } + + (*iptr) = l; + return x - (*iptr); +} + + +static void fmtfp (char *buffer, size_t *currlen, size_t maxlen, + LDOUBLE fvalue, int min, int max, int flags) +{ + int signvalue = 0; + double ufvalue; + char iconvert[311]; + char fconvert[311]; + int iplace = 0; + int fplace = 0; + int padlen = 0; /* amount to pad */ + int zpadlen = 0; + int caps = 0; + int idx; + double intpart; + double fracpart; + double temp; + + /* + * AIX manpage says the default is 0, but Solaris says the default + * is 6, and sprintf on AIX defaults to 6 + */ + if (max < 0) + max = 6; + + ufvalue = abs_val (fvalue); + + if (fvalue < 0) { + signvalue = '-'; + } else { + if (flags & DP_F_PLUS) { /* Do a sign (+/i) */ + signvalue = '+'; + } else { + if (flags & DP_F_SPACE) + signvalue = ' '; + } + } + +#if 0 + if (flags & DP_F_UP) caps = 1; /* Should characters be upper case? */ +#endif + +#if 0 + if (max == 0) ufvalue += 0.5; /* if max = 0 we must round */ +#endif + + /* + * Sorry, we only support 9 digits past the decimal because of our + * conversion method + */ + if (max > 9) + max = 9; + + /* We "cheat" by converting the fractional part to integer by + * multiplying by a factor of 10 + */ + + temp = ufvalue; + my_modf(temp, &intpart); + + fracpart = ROUND((POW10(max)) * (ufvalue - intpart)); + + if (fracpart >= POW10(max)) { + intpart++; + fracpart -= POW10(max); + } + + + /* Convert integer part */ + do { + temp = intpart*0.1; + my_modf(temp, &intpart); + idx = (int) ((temp -intpart +0.05)* 10.0); + /* idx = (int) (((double)(temp*0.1) -intpart +0.05) *10.0); */ + /* printf ("%llf, %f, %x\n", temp, intpart, idx); */ + iconvert[iplace++] = + (caps? "0123456789ABCDEF":"0123456789abcdef")[idx]; + } while (intpart && (iplace < 311)); + if (iplace == 311) iplace--; + iconvert[iplace] = 0; + + /* Convert fractional part */ + if (fracpart) + { + do { + temp = fracpart*0.1; + my_modf(temp, &fracpart); + idx = (int) ((temp -fracpart +0.05)* 10.0); + /* idx = (int) ((((temp/10) -fracpart) +0.05) *10); */ + /* printf ("%lf, %lf, %ld\n", temp, fracpart, idx ); */ + fconvert[fplace++] = + (caps? "0123456789ABCDEF":"0123456789abcdef")[idx]; + } while(fracpart && (fplace < 311)); + if (fplace == 311) fplace--; + } + fconvert[fplace] = 0; + + /* -1 for decimal point, another -1 if we are printing a sign */ + padlen = min - iplace - max - 1 - ((signvalue) ? 1 : 0); + zpadlen = max - fplace; + if (zpadlen < 0) zpadlen = 0; + if (padlen < 0) + padlen = 0; + if (flags & DP_F_MINUS) + padlen = -padlen; /* Left Justifty */ + + if ((flags & DP_F_ZERO) && (padlen > 0)) { + if (signvalue) { + dopr_outch (buffer, currlen, maxlen, signvalue); + --padlen; + signvalue = 0; + } + while (padlen > 0) { + dopr_outch (buffer, currlen, maxlen, '0'); + --padlen; + } + } + while (padlen > 0) { + dopr_outch (buffer, currlen, maxlen, ' '); + --padlen; + } + if (signvalue) + dopr_outch (buffer, currlen, maxlen, signvalue); + + while (iplace > 0) + dopr_outch (buffer, currlen, maxlen, iconvert[--iplace]); + +#ifdef DEBUG_SNPRINTF + printf("fmtfp: fplace=%d zpadlen=%d\n", fplace, zpadlen); +#endif + + /* + * Decimal point. This should probably use locale to find the correct + * char to print out. + */ + if (max > 0) { + dopr_outch (buffer, currlen, maxlen, '.'); + + while (zpadlen > 0) { + dopr_outch (buffer, currlen, maxlen, '0'); + --zpadlen; + } + + while (fplace > 0) + dopr_outch (buffer, currlen, maxlen, fconvert[--fplace]); + } + + while (padlen < 0) { + dopr_outch (buffer, currlen, maxlen, ' '); + ++padlen; + } +} + +static void dopr_outch(char *buffer, size_t *currlen, size_t maxlen, char c) +{ + if (*currlen < maxlen) { + buffer[(*currlen)] = c; + } + (*currlen)++; +} + +static struct pr_chunk *new_chunk(void) { + struct pr_chunk *new_c = (struct pr_chunk *)malloc(sizeof(struct pr_chunk)); + + if (!new_c) + return NULL; + + new_c->type = 0; + new_c->num = 0; + new_c->min = 0; + new_c->min_star = NULL; + new_c->max = -1; + new_c->max_star = NULL; + new_c->flags = 0; + new_c->cflags = 0; + new_c->start = 0; + new_c->len = 0; + new_c->value = 0; + new_c->fvalue = 0; + new_c->strvalue = NULL; + new_c->pnum = NULL; + new_c->next = NULL; + + return new_c; +} + +static int add_cnk_list_entry(struct pr_chunk_x **list, + int max_num, struct pr_chunk *chunk) { + struct pr_chunk_x *l; + struct pr_chunk **c; + int max; + int cnum; + int i, pos; + + if (chunk->num > max_num) { + max = chunk->num; + + if (*list == NULL) { + l = (struct pr_chunk_x *)malloc(sizeof(struct pr_chunk_x) * max); + pos = 0; + } else { + l = (struct pr_chunk_x *)realloc(*list, sizeof(struct pr_chunk_x) * max); + pos = max_num; + } + if (l == NULL) { + for (i = 0; i < max; i++) { + if ((*list)[i].chunks) free((*list)[i].chunks); + } + return 0; + } + for (i = pos; i < max; i++) { + l[i].chunks = NULL; + l[i].num = 0; + } + } else { + l = *list; + max = max_num; + } + + i = chunk->num - 1; + cnum = l[i].num + 1; + if (l[i].chunks == NULL) { + c = (struct pr_chunk **)malloc(sizeof(struct pr_chunk *) * cnum); + } else { + c = (struct pr_chunk **)realloc(l[i].chunks, sizeof(struct pr_chunk *) * cnum); + } + if (c == NULL) { + for (i = 0; i < max; i++) { + if (l[i].chunks) free(l[i].chunks); + } + return 0; + } + c[l[i].num] = chunk; + l[i].chunks = c; + l[i].num = cnum; + + *list = l; + return max; +} + + int rsync_vsnprintf (char *str, size_t count, const char *fmt, va_list args) +{ + return dopr(str, count, fmt, args); +} +#define vsnprintf rsync_vsnprintf +#endif + +/* yes this really must be a ||. Don't muck with this (tridge) + * + * The logic for these two is that we need our own definition if the + * OS *either* has no definition of *sprintf, or if it does have one + * that doesn't work properly according to the autoconf test. + */ +#if !defined(HAVE_SNPRINTF) || !defined(HAVE_C99_VSNPRINTF) +int rsync_snprintf(char *str,size_t count,const char *fmt,...) +{ + size_t ret; + va_list ap; + + va_start(ap, fmt); + ret = vsnprintf(str, count, fmt, ap); + va_end(ap); + return ret; +} +#define snprintf rsync_snprintf +#endif + +#ifndef HAVE_VASPRINTF + int vasprintf(char **ptr, const char *format, va_list ap) +{ + int ret; + va_list ap2; + + VA_COPY(ap2, ap); + ret = vsnprintf(NULL, 0, format, ap2); + va_end(ap2); + if (ret < 0) return ret; + + (*ptr) = (char *)malloc(ret+1); + if (!*ptr) return -1; + + VA_COPY(ap2, ap); + ret = vsnprintf(*ptr, ret+1, format, ap2); + va_end(ap2); + + return ret; +} +#endif + + +#ifndef HAVE_ASPRINTF + int asprintf(char **ptr, const char *format, ...) +{ + va_list ap; + int ret; + + *ptr = NULL; + va_start(ap, format); + ret = vasprintf(ptr, format, ap); + va_end(ap); + + return ret; +} +#endif + +#ifdef TEST_SNPRINTF + + int sprintf(char *str,const char *fmt,...); + int printf(const char *fmt,...); + + int main (void) +{ + char buf1[1024]; + char buf2[1024]; + char *buf3; + char *fp_fmt[] = { + "%1.1f", + "%-1.5f", + "%1.5f", + "%123.9f", + "%10.5f", + "% 10.5f", + "%+22.9f", + "%+4.9f", + "%01.3f", + "%4f", + "%3.1f", + "%3.2f", + "%.0f", + "%f", + "%-8.8f", + "%-9.9f", + NULL + }; + double fp_nums[] = { 6442452944.1234, -1.5, 134.21, 91340.2, 341.1234, 203.9, 0.96, 0.996, + 0.9996, 1.996, 4.136, 5.030201, 0.00205, + /* END LIST */ 0}; + char *int_fmt[] = { + "%-1.5d", + "%1.5d", + "%123.9d", + "%5.5d", + "%10.5d", + "% 10.5d", + "%+22.33d", + "%01.3d", + "%4d", + "%d", + NULL + }; + long int_nums[] = { -1, 134, 91340, 341, 0203, 1234567890, 0}; + char *str_fmt[] = { + "%10.5s", + "%-10.5s", + "%5.10s", + "%-5.10s", + "%10.1s", + "%0.10s", + "%10.0s", + "%1.10s", + "%s", + "%.1s", + "%.10s", + "%10s", + NULL + }; + char *str_vals[] = {"hello", "a", "", "a longer string", NULL}; +#ifdef HAVE_LONG_LONG + char *ll_fmt[] = { + "%llu", + NULL + }; + LLONG ll_nums[] = { 134, 91340, 341, 0203, 1234567890, 128006186140000000LL, 0}; +#endif + int x, y; + int fail = 0; + int num = 0; + int l1, l2; + char *ss_fmt[] = { + "%zd", + "%zu", + NULL + }; + size_t ss_nums[] = {134, 91340, 123456789, 0203, 1234567890, 0}; + + printf ("Testing snprintf format codes against system sprintf...\n"); + + for (x = 0; fp_fmt[x] ; x++) { + for (y = 0; fp_nums[y] != 0 ; y++) { + buf1[0] = buf2[0] = '\0'; + l1 = snprintf(buf1, sizeof(buf1), fp_fmt[x], fp_nums[y]); + l2 = sprintf (buf2, fp_fmt[x], fp_nums[y]); + buf1[1023] = buf2[1023] = '\0'; + if (strcmp (buf1, buf2) || (l1 != l2)) { + printf("snprintf doesn't match Format: %s\n\tsnprintf(%d) = [%s]\n\t sprintf(%d) = [%s]\n", + fp_fmt[x], l1, buf1, l2, buf2); + fail++; + } + num++; + } + } + + for (x = 0; int_fmt[x] ; x++) { + for (y = 0; int_nums[y] != 0 ; y++) { + buf1[0] = buf2[0] = '\0'; + l1 = snprintf(buf1, sizeof(buf1), int_fmt[x], int_nums[y]); + l2 = sprintf (buf2, int_fmt[x], int_nums[y]); + buf1[1023] = buf2[1023] = '\0'; + if (strcmp (buf1, buf2) || (l1 != l2)) { + printf("snprintf doesn't match Format: %s\n\tsnprintf(%d) = [%s]\n\t sprintf(%d) = [%s]\n", + int_fmt[x], l1, buf1, l2, buf2); + fail++; + } + num++; + } + } + + for (x = 0; str_fmt[x] ; x++) { + for (y = 0; str_vals[y] != 0 ; y++) { + buf1[0] = buf2[0] = '\0'; + l1 = snprintf(buf1, sizeof(buf1), str_fmt[x], str_vals[y]); + l2 = sprintf (buf2, str_fmt[x], str_vals[y]); + buf1[1023] = buf2[1023] = '\0'; + if (strcmp (buf1, buf2) || (l1 != l2)) { + printf("snprintf doesn't match Format: %s\n\tsnprintf(%d) = [%s]\n\t sprintf(%d) = [%s]\n", + str_fmt[x], l1, buf1, l2, buf2); + fail++; + } + num++; + } + } + +#ifdef HAVE_LONG_LONG + for (x = 0; ll_fmt[x] ; x++) { + for (y = 0; ll_nums[y] != 0 ; y++) { + buf1[0] = buf2[0] = '\0'; + l1 = snprintf(buf1, sizeof(buf1), ll_fmt[x], ll_nums[y]); + l2 = sprintf (buf2, ll_fmt[x], ll_nums[y]); + buf1[1023] = buf2[1023] = '\0'; + if (strcmp (buf1, buf2) || (l1 != l2)) { + printf("snprintf doesn't match Format: %s\n\tsnprintf(%d) = [%s]\n\t sprintf(%d) = [%s]\n", + ll_fmt[x], l1, buf1, l2, buf2); + fail++; + } + num++; + } + } +#endif + +#define BUFSZ 2048 + + buf1[0] = buf2[0] = '\0'; + if ((buf3 = malloc(BUFSZ)) == NULL) { + fail++; + } else { + num++; + memset(buf3, 'a', BUFSZ); + snprintf(buf1, sizeof(buf1), "%.*s", 1, buf3); + buf1[1023] = '\0'; + if (strcmp(buf1, "a") != 0) { + printf("length limit buf1 '%s' expected 'a'\n", buf1); + fail++; + } + } + + buf1[0] = buf2[0] = '\0'; + l1 = snprintf(buf1, sizeof(buf1), "%4$*1$d %2$s %3$*1$.*1$f", 3, "pos test", 12.3456, 9); + l2 = sprintf(buf2, "%4$*1$d %2$s %3$*1$.*1$f", 3, "pos test", 12.3456, 9); + buf1[1023] = buf2[1023] = '\0'; + if (strcmp(buf1, buf2) || (l1 != l2)) { + printf("snprintf doesn't match Format: %s\n\tsnprintf(%d) = [%s]\n\t sprintf(%d) = [%s]\n", + "%4$*1$d %2$s %3$*1$.*1$f", l1, buf1, l2, buf2); + fail++; + } + + buf1[0] = buf2[0] = '\0'; + l1 = snprintf(buf1, sizeof(buf1), "%4$*4$d %2$s %3$*4$.*4$f", 3, "pos test", 12.3456, 9); + l2 = sprintf(buf2, "%4$*4$d %2$s %3$*4$.*4$f", 3, "pos test", 12.3456, 9); + buf1[1023] = buf2[1023] = '\0'; + if (strcmp(buf1, buf2)) { + printf("snprintf doesn't match Format: %s\n\tsnprintf(%d) = [%s]\n\t sprintf(%d) = [%s]\n", + "%4$*1$d %2$s %3$*1$.*1$f", l1, buf1, l2, buf2); + fail++; + } + + for (x = 0; ss_fmt[x] ; x++) { + for (y = 0; ss_nums[y] != 0 ; y++) { + buf1[0] = buf2[0] = '\0'; + l1 = snprintf(buf1, sizeof(buf1), ss_fmt[x], ss_nums[y]); + l2 = sprintf (buf2, ss_fmt[x], ss_nums[y]); + buf1[1023] = buf2[1023] = '\0'; + if (strcmp (buf1, buf2) || (l1 != l2)) { + printf("snprintf doesn't match Format: %s\n\tsnprintf(%d) = [%s]\n\t sprintf(%d) = [%s]\n", + ss_fmt[x], l1, buf1, l2, buf2); + fail++; + } + num++; + } + } +#if 0 + buf1[0] = buf2[0] = '\0'; + l1 = snprintf(buf1, sizeof(buf1), "%lld", (LLONG)1234567890); + l2 = sprintf(buf2, "%lld", (LLONG)1234567890); + buf1[1023] = buf2[1023] = '\0'; + if (strcmp(buf1, buf2)) { + printf("snprintf doesn't match Format: %s\n\tsnprintf(%d) = [%s]\n\t sprintf(%d) = [%s]\n", + "%lld", l1, buf1, l2, buf2); + fail++; + } + + buf1[0] = buf2[0] = '\0'; + l1 = snprintf(buf1, sizeof(buf1), "%Lf", (LDOUBLE)890.1234567890123); + l2 = sprintf(buf2, "%Lf", (LDOUBLE)890.1234567890123); + buf1[1023] = buf2[1023] = '\0'; + if (strcmp(buf1, buf2)) { + printf("snprintf doesn't match Format: %s\n\tsnprintf(%d) = [%s]\n\t sprintf(%d) = [%s]\n", + "%Lf", l1, buf1, l2, buf2); + fail++; + } +#endif + printf ("%d tests failed out of %d.\n", fail, num); + + printf("seeing how many digits we support\n"); + { + double v0 = 0.12345678901234567890123456789012345678901; + for (x=0; x<100; x++) { + double p = pow(10, x); + double r = v0*p; + snprintf(buf1, sizeof(buf1), "%1.1f", r); + sprintf(buf2, "%1.1f", r); + if (strcmp(buf1, buf2)) { + printf("we seem to support %d digits\n", x-1); + break; + } + } + } + + return 0; +} +#endif /* TEST_SNPRINTF */ diff --git a/rsync/lib/sysacls.c b/rsync/lib/sysacls.c new file mode 100644 index 0000000..6ccfe43 --- /dev/null +++ b/rsync/lib/sysacls.c @@ -0,0 +1,2796 @@ +/* + * Unix SMB/CIFS implementation. + * Based on the Samba ACL support code. + * Copyright (C) Jeremy Allison 2000. + * Copyright (C) 2007-2014 Wayne Davison + * + * The permission functions have been changed to get/set all bits via + * one call. Some functions that rsync doesn't need were also removed. + * + * 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 + * with this program; if not, visit the http://fsf.org website. + */ + +#include "rsync.h" +#include "sysacls.h" + +#ifdef SUPPORT_ACLS + +#ifdef DEBUG +#undef DEBUG +#endif +#define DEBUG(x,y) + +void SAFE_FREE(void *mem) +{ + if (mem) + free(mem); +} + +/* + This file wraps all differing system ACL interfaces into a consistent + one based on the POSIX interface. It also returns the correct errors + for older UNIX systems that don't support ACLs. + + The interfaces that each ACL implementation must support are as follows : + + int sys_acl_get_entry( SMB_ACL_T theacl, int entry_id, SMB_ACL_ENTRY_T *entry_p) + int sys_acl_get_tag_type( SMB_ACL_ENTRY_T entry_d, SMB_ACL_TAG_T *tag_type_p) + int sys_acl_get_info(SMB_ACL_ENTRY_T entry, SMB_ACL_TAG_T *tag_type_p, uint32 *bits_p, id_t *u_g_id_p) + SMB_ACL_T sys_acl_get_file( const char *path_p, SMB_ACL_TYPE_T type) + SMB_ACL_T sys_acl_get_fd(int fd) + SMB_ACL_T sys_acl_init( int count) + int sys_acl_create_entry( SMB_ACL_T *pacl, SMB_ACL_ENTRY_T *pentry) + int sys_acl_set_info(SMB_ACL_ENTRY_T entry, SMB_ACL_TAG_T tag_type, uint32 bits, id_t u_g_id) + int sys_acl_set_access_bits(SMB_ACL_ENTRY_T entry, uint32 bits) + int sys_acl_valid( SMB_ACL_T theacl ) + int sys_acl_set_file( const char *name, SMB_ACL_TYPE_T acltype, SMB_ACL_T theacl) + int sys_acl_set_fd( int fd, SMB_ACL_T theacl) + int sys_acl_delete_def_file(const char *path) + int sys_acl_free_acl(SMB_ACL_T posix_acl) + +*/ + +#if defined(HAVE_POSIX_ACLS) /*--------------------------------------------*/ + +/* Identity mapping - easy. */ + +int sys_acl_get_entry( SMB_ACL_T the_acl, int entry_id, SMB_ACL_ENTRY_T *entry_p) +{ + return acl_get_entry( the_acl, entry_id, entry_p); +} + +int sys_acl_get_tag_type( SMB_ACL_ENTRY_T entry_d, SMB_ACL_TAG_T *tag_type_p) +{ + return acl_get_tag_type( entry_d, tag_type_p); +} + +SMB_ACL_T sys_acl_get_file( const char *path_p, SMB_ACL_TYPE_T type) +{ + return acl_get_file( path_p, type); +} + +#if 0 +SMB_ACL_T sys_acl_get_fd(int fd) +{ + return acl_get_fd(fd); +} +#endif + +#if defined(HAVE_ACL_GET_PERM_NP) +#define acl_get_perm(p, b) acl_get_perm_np(p, b) +#endif + +int sys_acl_get_info(SMB_ACL_ENTRY_T entry, SMB_ACL_TAG_T *tag_type_p, uint32 *bits_p, id_t *u_g_id_p) +{ + acl_permset_t permset; + + if (acl_get_tag_type(entry, tag_type_p) != 0 + || acl_get_permset(entry, &permset) != 0) + return -1; + + *bits_p = (acl_get_perm(permset, ACL_READ) ? 4 : 0) + | (acl_get_perm(permset, ACL_WRITE) ? 2 : 0) + | (acl_get_perm(permset, ACL_EXECUTE) ? 1 : 0); + + if (*tag_type_p == SMB_ACL_USER || *tag_type_p == SMB_ACL_GROUP) { + void *qual; + if ((qual = acl_get_qualifier(entry)) == NULL) + return -1; + *u_g_id_p = *(id_t*)qual; + acl_free(qual); + } + + return 0; +} + +SMB_ACL_T sys_acl_init( int count) +{ + return acl_init(count); +} + +int sys_acl_create_entry( SMB_ACL_T *pacl, SMB_ACL_ENTRY_T *pentry) +{ + return acl_create_entry(pacl, pentry); +} + +int sys_acl_set_info(SMB_ACL_ENTRY_T entry, SMB_ACL_TAG_T tag_type, uint32 bits, id_t u_g_id) +{ + if (acl_set_tag_type(entry, tag_type) != 0) + return -1; + + if (tag_type == SMB_ACL_USER || tag_type == SMB_ACL_GROUP) { + if (acl_set_qualifier(entry, (void*)&u_g_id) != 0) + return -1; + } + + return sys_acl_set_access_bits(entry, bits); +} + +int sys_acl_set_access_bits(SMB_ACL_ENTRY_T entry, uint32 bits) +{ + acl_permset_t permset; + int rc; + if ((rc = acl_get_permset(entry, &permset)) != 0) + return rc; + acl_clear_perms(permset); + if (bits & 4) + acl_add_perm(permset, ACL_READ); + if (bits & 2) + acl_add_perm(permset, ACL_WRITE); + if (bits & 1) + acl_add_perm(permset, ACL_EXECUTE); + return acl_set_permset(entry, permset); +} + +int sys_acl_valid( SMB_ACL_T theacl ) +{ + return acl_valid(theacl); +} + +int sys_acl_set_file(const char *name, SMB_ACL_TYPE_T acltype, SMB_ACL_T theacl) +{ + return acl_set_file(name, acltype, theacl); +} + +#if 0 +int sys_acl_set_fd( int fd, SMB_ACL_T theacl) +{ + return acl_set_fd(fd, theacl); +} +#endif + +int sys_acl_delete_def_file(const char *name) +{ + return acl_delete_def_file(name); +} + +int sys_acl_free_acl(SMB_ACL_T the_acl) +{ + return acl_free(the_acl); +} + +#elif defined(HAVE_TRU64_ACLS) /*--------------------------------------------*/ +/* + * The interface to DEC/Compaq Tru64 UNIX ACLs + * is based on Draft 13 of the POSIX spec which is + * slightly different from the Draft 16 interface. + * + * Also, some of the permset manipulation functions + * such as acl_clear_perm() and acl_add_perm() appear + * to be broken on Tru64 so we have to manipulate + * the permission bits in the permset directly. + */ +int sys_acl_get_entry( SMB_ACL_T the_acl, int entry_id, SMB_ACL_ENTRY_T *entry_p) +{ + SMB_ACL_ENTRY_T entry; + + if (entry_id == SMB_ACL_FIRST_ENTRY && acl_first_entry(the_acl) != 0) { + return -1; + } + + errno = 0; + if ((entry = acl_get_entry(the_acl)) != NULL) { + *entry_p = entry; + return 1; + } + + return errno ? -1 : 0; +} + +int sys_acl_get_tag_type( SMB_ACL_ENTRY_T entry_d, SMB_ACL_TAG_T *tag_type_p) +{ + return acl_get_tag_type( entry_d, tag_type_p); +} + +SMB_ACL_T sys_acl_get_file( const char *path_p, SMB_ACL_TYPE_T type) +{ + return acl_get_file((char *)path_p, type); +} + +#if 0 +SMB_ACL_T sys_acl_get_fd(int fd) +{ + return acl_get_fd(fd, ACL_TYPE_ACCESS); +} +#endif + +int sys_acl_get_info(SMB_ACL_ENTRY_T entry, SMB_ACL_TAG_T *tag_type_p, uint32 *bits_p, id_t *u_g_id_p) +{ + acl_permset_t permset; + + if (acl_get_tag_type(entry, tag_type_p) != 0 + || acl_get_permset(entry, &permset) != 0) + return -1; + + *bits_p = *permset & 7; /* Tru64 doesn't have acl_get_perm() */ + + if (*tag_type_p == SMB_ACL_USER || *tag_type_p == SMB_ACL_GROUP) { + void *qual; + if ((qual = acl_get_qualifier(entry)) == NULL) + return -1; + *u_g_id_p = *(id_t*)qual; + acl_free_qualifier(qual, *tag_type_p); + } + + return 0; +} + +SMB_ACL_T sys_acl_init( int count) +{ + return acl_init(count); +} + +int sys_acl_create_entry( SMB_ACL_T *pacl, SMB_ACL_ENTRY_T *pentry) +{ + SMB_ACL_ENTRY_T entry; + + if ((entry = acl_create_entry(pacl)) == NULL) { + return -1; + } + + *pentry = entry; + return 0; +} + +int sys_acl_set_info(SMB_ACL_ENTRY_T entry, SMB_ACL_TAG_T tag_type, uint32 bits, id_t u_g_id) +{ + if (acl_set_tag_type(entry, tag_type) != 0) + return -1; + + if (tag_type == SMB_ACL_USER || tag_type == SMB_ACL_GROUP) { + if (acl_set_qualifier(entry, (void*)&u_g_id) != 0) + return -1; + } + + return sys_acl_set_access_bits(entry, bits); +} + +int sys_acl_set_access_bits(SMB_ACL_ENTRY_T entry, uint32 bits) +{ + acl_permset_t permset; + int rc; + if ((rc = acl_get_permset(entry, &permset)) != 0) + return rc; + *permset = bits & 7; + return acl_set_permset(entry, permset); +} + +int sys_acl_valid( SMB_ACL_T theacl ) +{ + acl_entry_t entry; + + return acl_valid(theacl, &entry); +} + +int sys_acl_set_file( const char *name, SMB_ACL_TYPE_T acltype, SMB_ACL_T theacl) +{ + return acl_set_file((char *)name, acltype, theacl); +} + +#if 0 +int sys_acl_set_fd( int fd, SMB_ACL_T theacl) +{ + return acl_set_fd(fd, ACL_TYPE_ACCESS, theacl); +} +#endif + +int sys_acl_delete_def_file(const char *name) +{ + return acl_delete_def_file((char *)name); +} + +int sys_acl_free_acl(SMB_ACL_T the_acl) +{ + return acl_free(the_acl); +} + +#elif defined(HAVE_UNIXWARE_ACLS) || defined(HAVE_SOLARIS_ACLS) /*-----------*/ + +/* + * Donated by Michael Davidson for UnixWare / OpenUNIX. + * Modified by Toomas Soome for Solaris. + */ + +/* + * Note that while this code implements sufficient functionality + * to support the sys_acl_* interfaces it does not provide all + * of the semantics of the POSIX ACL interfaces. + * + * In particular, an ACL entry descriptor (SMB_ACL_ENTRY_T) returned + * from a call to sys_acl_get_entry() should not be assumed to be + * valid after calling any of the following functions, which may + * reorder the entries in the ACL. + * + * sys_acl_valid() + * sys_acl_set_file() + * sys_acl_set_fd() + */ + +/* + * The only difference between Solaris and UnixWare / OpenUNIX is + * that the #defines for the ACL operations have different names + */ +#if defined(HAVE_UNIXWARE_ACLS) + +#define SETACL ACL_SET +#define GETACL ACL_GET +#define GETACLCNT ACL_CNT + +#endif + + +int sys_acl_get_entry(SMB_ACL_T acl_d, int entry_id, SMB_ACL_ENTRY_T *entry_p) +{ + if (entry_id != SMB_ACL_FIRST_ENTRY && entry_id != SMB_ACL_NEXT_ENTRY) { + errno = EINVAL; + return -1; + } + + if (entry_p == NULL) { + errno = EINVAL; + return -1; + } + + if (entry_id == SMB_ACL_FIRST_ENTRY) { + acl_d->next = 0; + } + + if (acl_d->next < 0) { + errno = EINVAL; + return -1; + } + + if (acl_d->next >= acl_d->count) { + return 0; + } + + *entry_p = &acl_d->acl[acl_d->next++]; + + return 1; +} + +int sys_acl_get_tag_type(SMB_ACL_ENTRY_T entry_d, SMB_ACL_TAG_T *type_p) +{ + *type_p = entry_d->a_type; + + return 0; +} + +/* + * There is no way of knowing what size the ACL returned by + * GETACL will be unless you first call GETACLCNT which means + * making an additional system call. + * + * In the hope of avoiding the cost of the additional system + * call in most cases, we initially allocate enough space for + * an ACL with INITIAL_ACL_SIZE entries. If this turns out to + * be too small then we use GETACLCNT to find out the actual + * size, reallocate the ACL buffer, and then call GETACL again. + */ + +#define INITIAL_ACL_SIZE 16 + +SMB_ACL_T sys_acl_get_file(const char *path_p, SMB_ACL_TYPE_T type) +{ + SMB_ACL_T acl_d; + int count; /* # of ACL entries allocated */ + int naccess; /* # of access ACL entries */ + int ndefault; /* # of default ACL entries */ + + if (type != SMB_ACL_TYPE_ACCESS && type != SMB_ACL_TYPE_DEFAULT) { + errno = EINVAL; + return NULL; + } + + count = INITIAL_ACL_SIZE; + if ((acl_d = sys_acl_init(count)) == NULL) { + return NULL; + } + + /* + * If there isn't enough space for the ACL entries we use + * GETACLCNT to determine the actual number of ACL entries + * reallocate and try again. This is in a loop because it + * is possible that someone else could modify the ACL and + * increase the number of entries between the call to + * GETACLCNT and the call to GETACL. + */ + while ((count = acl(path_p, GETACL, count, &acl_d->acl[0])) < 0 + && errno == ENOSPC) { + + sys_acl_free_acl(acl_d); + + if ((count = acl(path_p, GETACLCNT, 0, NULL)) < 0) { + return NULL; + } + + if ((acl_d = sys_acl_init(count)) == NULL) { + return NULL; + } + } + + if (count < 0) { + sys_acl_free_acl(acl_d); + return NULL; + } + + /* + * calculate the number of access and default ACL entries + * + * Note: we assume that the acl() system call returned a + * well formed ACL which is sorted so that all of the + * access ACL entries preceed any default ACL entries + */ + for (naccess = 0; naccess < count; naccess++) { + if (acl_d->acl[naccess].a_type & ACL_DEFAULT) + break; + } + ndefault = count - naccess; + + /* + * if the caller wants the default ACL we have to copy + * the entries down to the start of the acl[] buffer + * and mask out the ACL_DEFAULT flag from the type field + */ + if (type == SMB_ACL_TYPE_DEFAULT) { + int i, j; + + for (i = 0, j = naccess; i < ndefault; i++, j++) { + acl_d->acl[i] = acl_d->acl[j]; + acl_d->acl[i].a_type &= ~ACL_DEFAULT; + } + + acl_d->count = ndefault; + } else { + acl_d->count = naccess; + } + + return acl_d; +} + +#if 0 +SMB_ACL_T sys_acl_get_fd(int fd) +{ + SMB_ACL_T acl_d; + int count; /* # of ACL entries allocated */ + int naccess; /* # of access ACL entries */ + + count = INITIAL_ACL_SIZE; + if ((acl_d = sys_acl_init(count)) == NULL) { + return NULL; + } + + while ((count = facl(fd, GETACL, count, &acl_d->acl[0])) < 0 + && errno == ENOSPC) { + + sys_acl_free_acl(acl_d); + + if ((count = facl(fd, GETACLCNT, 0, NULL)) < 0) { + return NULL; + } + + if ((acl_d = sys_acl_init(count)) == NULL) { + return NULL; + } + } + + if (count < 0) { + sys_acl_free_acl(acl_d); + return NULL; + } + + /* + * calculate the number of access ACL entries + */ + for (naccess = 0; naccess < count; naccess++) { + if (acl_d->acl[naccess].a_type & ACL_DEFAULT) + break; + } + + acl_d->count = naccess; + + return acl_d; +} +#endif + +int sys_acl_get_info(SMB_ACL_ENTRY_T entry, SMB_ACL_TAG_T *tag_type_p, uint32 *bits_p, id_t *u_g_id_p) +{ + *tag_type_p = entry->a_type; + + *bits_p = entry->a_perm; + + if (*tag_type_p == SMB_ACL_USER || *tag_type_p == SMB_ACL_GROUP) + *u_g_id_p = entry->a_id; + + return 0; +} + +SMB_ACL_T sys_acl_init(int count) +{ + SMB_ACL_T a; + + if (count < 0) { + errno = EINVAL; + return NULL; + } + + /* + * note that since the definition of the structure pointed + * to by the SMB_ACL_T includes the first element of the + * acl[] array, this actually allocates an ACL with room + * for (count+1) entries + */ + if ((a = (SMB_ACL_T)SMB_MALLOC(sizeof a[0] + count * sizeof (struct acl))) == NULL) { + errno = ENOMEM; + return NULL; + } + + a->size = count + 1; + a->count = 0; + a->next = -1; + + return a; +} + + +int sys_acl_create_entry(SMB_ACL_T *acl_p, SMB_ACL_ENTRY_T *entry_p) +{ + SMB_ACL_T acl_d; + SMB_ACL_ENTRY_T entry_d; + + if (acl_p == NULL || entry_p == NULL || (acl_d = *acl_p) == NULL) { + errno = EINVAL; + return -1; + } + + if (acl_d->count >= acl_d->size) { + errno = ENOSPC; + return -1; + } + + entry_d = &acl_d->acl[acl_d->count++]; + entry_d->a_type = 0; + entry_d->a_id = -1; + entry_d->a_perm = 0; + *entry_p = entry_d; + + return 0; +} + +int sys_acl_set_info(SMB_ACL_ENTRY_T entry, SMB_ACL_TAG_T tag_type, uint32 bits, id_t u_g_id) +{ + entry->a_type = tag_type; + + if (tag_type == SMB_ACL_USER || tag_type == SMB_ACL_GROUP) + entry->a_id = u_g_id; + + entry->a_perm = bits; + + return 0; +} + +int sys_acl_set_access_bits(SMB_ACL_ENTRY_T entry_d, uint32 bits) +{ + entry_d->a_perm = bits; + return 0; +} + +/* + * sort the ACL and check it for validity + * + * if it's a minimal ACL with only 4 entries then we + * need to recalculate the mask permissions to make + * sure that they are the same as the GROUP_OBJ + * permissions as required by the UnixWare acl() system call. + * + * (note: since POSIX allows minimal ACLs which only contain + * 3 entries - ie there is no mask entry - we should, in theory, + * check for this and add a mask entry if necessary - however + * we "know" that the caller of this interface always specifies + * a mask so, in practice "this never happens" (tm) - if it *does* + * happen aclsort() will fail and return an error and someone will + * have to fix it ...) + */ + +static int acl_sort(SMB_ACL_T acl_d) +{ + int fixmask = (acl_d->count <= 4); + + if (aclsort(acl_d->count, fixmask, acl_d->acl) != 0) { + errno = EINVAL; + return -1; + } + return 0; +} + +int sys_acl_valid(SMB_ACL_T acl_d) +{ + return acl_sort(acl_d); +} + +int sys_acl_set_file(const char *name, SMB_ACL_TYPE_T type, SMB_ACL_T acl_d) +{ + struct stat s; + struct acl *acl_p; + int acl_count; + struct acl *acl_buf = NULL; + int ret; + + if (type != SMB_ACL_TYPE_ACCESS && type != SMB_ACL_TYPE_DEFAULT) { + errno = EINVAL; + return -1; + } + + if (acl_sort(acl_d) != 0) { + return -1; + } + + acl_p = &acl_d->acl[0]; + acl_count = acl_d->count; + + /* + * if it's a directory there is extra work to do + * since the acl() system call will replace both + * the access ACLs and the default ACLs (if any) + */ + if (stat(name, &s) != 0) { + return -1; + } + if (S_ISDIR(s.st_mode)) { + SMB_ACL_T acc_acl; + SMB_ACL_T def_acl; + SMB_ACL_T tmp_acl; + int i; + + if (type == SMB_ACL_TYPE_ACCESS) { + acc_acl = acl_d; + def_acl = tmp_acl = sys_acl_get_file(name, SMB_ACL_TYPE_DEFAULT); + + } else { + def_acl = acl_d; + acc_acl = tmp_acl = sys_acl_get_file(name, SMB_ACL_TYPE_ACCESS); + } + + if (tmp_acl == NULL) { + return -1; + } + + /* + * allocate a temporary buffer for the complete ACL + */ + acl_count = acc_acl->count + def_acl->count; + acl_p = acl_buf = SMB_MALLOC_ARRAY(struct acl, acl_count); + + if (acl_buf == NULL) { + sys_acl_free_acl(tmp_acl); + errno = ENOMEM; + return -1; + } + + /* + * copy the access control and default entries into the buffer + */ + memcpy(&acl_buf[0], &acc_acl->acl[0], + acc_acl->count * sizeof(acl_buf[0])); + + memcpy(&acl_buf[acc_acl->count], &def_acl->acl[0], + def_acl->count * sizeof(acl_buf[0])); + + /* + * set the ACL_DEFAULT flag on the default entries + */ + for (i = acc_acl->count; i < acl_count; i++) { + acl_buf[i].a_type |= ACL_DEFAULT; + } + + sys_acl_free_acl(tmp_acl); + + } else if (type != SMB_ACL_TYPE_ACCESS) { + errno = EINVAL; + return -1; + } + + ret = acl(name, SETACL, acl_count, acl_p); + + SAFE_FREE(acl_buf); + + return ret; +} + +#if 0 +int sys_acl_set_fd(int fd, SMB_ACL_T acl_d) +{ + if (acl_sort(acl_d) != 0) { + return -1; + } + + return facl(fd, SETACL, acl_d->count, &acl_d->acl[0]); +} +#endif + +int sys_acl_delete_def_file(const char *path) +{ + SMB_ACL_T acl_d; + int ret; + + /* + * fetching the access ACL and rewriting it has + * the effect of deleting the default ACL + */ + if ((acl_d = sys_acl_get_file(path, SMB_ACL_TYPE_ACCESS)) == NULL) { + return -1; + } + + ret = acl(path, SETACL, acl_d->count, acl_d->acl); + + sys_acl_free_acl(acl_d); + + return ret; +} + +int sys_acl_free_acl(SMB_ACL_T acl_d) +{ + SAFE_FREE(acl_d); + return 0; +} + +#elif defined(HAVE_HPUX_ACLS) /*---------------------------------------------*/ + +#include + +/* + * Based on the Solaris/SCO code - with modifications. + */ + +/* + * Note that while this code implements sufficient functionality + * to support the sys_acl_* interfaces it does not provide all + * of the semantics of the POSIX ACL interfaces. + * + * In particular, an ACL entry descriptor (SMB_ACL_ENTRY_T) returned + * from a call to sys_acl_get_entry() should not be assumed to be + * valid after calling any of the following functions, which may + * reorder the entries in the ACL. + * + * sys_acl_valid() + * sys_acl_set_file() + * sys_acl_set_fd() + */ + +/* This checks if the POSIX ACL system call is defined */ +/* which basically corresponds to whether JFS 3.3 or */ +/* higher is installed. If acl() was called when it */ +/* isn't defined, it causes the process to core dump */ +/* so it is important to check this and avoid acl() */ +/* calls if it isn't there. */ + +static BOOL hpux_acl_call_presence(void) +{ + + shl_t handle = NULL; + void *value; + int ret_val=0; + static BOOL already_checked=0; + + if(already_checked) + return True; + + + ret_val = shl_findsym(&handle, "acl", TYPE_PROCEDURE, &value); + + if(ret_val != 0) { + DEBUG(5, ("hpux_acl_call_presence: shl_findsym() returned %d, errno = %d, error %s\n", + ret_val, errno, strerror(errno))); + DEBUG(5,("hpux_acl_call_presence: acl() system call is not present. Check if you have JFS 3.3 and above?\n")); + return False; + } + + DEBUG(10,("hpux_acl_call_presence: acl() system call is present. We have JFS 3.3 or above \n")); + + already_checked = True; + return True; +} + +int sys_acl_get_entry(SMB_ACL_T acl_d, int entry_id, SMB_ACL_ENTRY_T *entry_p) +{ + if (entry_id != SMB_ACL_FIRST_ENTRY && entry_id != SMB_ACL_NEXT_ENTRY) { + errno = EINVAL; + return -1; + } + + if (entry_p == NULL) { + errno = EINVAL; + return -1; + } + + if (entry_id == SMB_ACL_FIRST_ENTRY) { + acl_d->next = 0; + } + + if (acl_d->next < 0) { + errno = EINVAL; + return -1; + } + + if (acl_d->next >= acl_d->count) { + return 0; + } + + *entry_p = &acl_d->acl[acl_d->next++]; + + return 1; +} + +int sys_acl_get_tag_type(SMB_ACL_ENTRY_T entry_d, SMB_ACL_TAG_T *type_p) +{ + *type_p = entry_d->a_type; + + return 0; +} + +/* + * There is no way of knowing what size the ACL returned by + * ACL_GET will be unless you first call ACL_CNT which means + * making an additional system call. + * + * In the hope of avoiding the cost of the additional system + * call in most cases, we initially allocate enough space for + * an ACL with INITIAL_ACL_SIZE entries. If this turns out to + * be too small then we use ACL_CNT to find out the actual + * size, reallocate the ACL buffer, and then call ACL_GET again. + */ + +#define INITIAL_ACL_SIZE 16 + +#ifndef NACLENTRIES +#define NACLENTRIES 0 +#endif + +SMB_ACL_T sys_acl_get_file(const char *path_p, SMB_ACL_TYPE_T type) +{ + SMB_ACL_T acl_d; + int count; /* # of ACL entries allocated */ + int naccess; /* # of access ACL entries */ + int ndefault; /* # of default ACL entries */ + + if(hpux_acl_call_presence() == False) { + /* Looks like we don't have the acl() system call on HPUX. + * May be the system doesn't have the latest version of JFS. + */ + return NULL; + } + + if (type != SMB_ACL_TYPE_ACCESS && type != SMB_ACL_TYPE_DEFAULT) { + errno = EINVAL; + return NULL; + } + + count = INITIAL_ACL_SIZE; + if ((acl_d = sys_acl_init(count)) == NULL) { + return NULL; + } + + /* + * If there isn't enough space for the ACL entries we use + * ACL_CNT to determine the actual number of ACL entries + * reallocate and try again. This is in a loop because it + * is possible that someone else could modify the ACL and + * increase the number of entries between the call to + * ACL_CNT and the call to ACL_GET. + */ + while ((count = acl(path_p, ACL_GET, count, &acl_d->acl[0])) < 0 && errno == ENOSPC) { + + sys_acl_free_acl(acl_d); + + if ((count = acl(path_p, ACL_CNT, NACLENTRIES, NULL)) < 0) { + return NULL; + } + + if ((acl_d = sys_acl_init(count)) == NULL) { + return NULL; + } + } + + if (count < 0) { + sys_acl_free_acl(acl_d); + return NULL; + } + + /* + * calculate the number of access and default ACL entries + * + * Note: we assume that the acl() system call returned a + * well formed ACL which is sorted so that all of the + * access ACL entries preceed any default ACL entries + */ + for (naccess = 0; naccess < count; naccess++) { + if (acl_d->acl[naccess].a_type & ACL_DEFAULT) + break; + } + ndefault = count - naccess; + + /* + * if the caller wants the default ACL we have to copy + * the entries down to the start of the acl[] buffer + * and mask out the ACL_DEFAULT flag from the type field + */ + if (type == SMB_ACL_TYPE_DEFAULT) { + int i, j; + + for (i = 0, j = naccess; i < ndefault; i++, j++) { + acl_d->acl[i] = acl_d->acl[j]; + acl_d->acl[i].a_type &= ~ACL_DEFAULT; + } + + acl_d->count = ndefault; + } else { + acl_d->count = naccess; + } + + return acl_d; +} + +#if 0 +SMB_ACL_T sys_acl_get_fd(int fd) +{ + /* + * HPUX doesn't have the facl call. Fake it using the path.... JRA. + */ + + files_struct *fsp = file_find_fd(fd); + + if (fsp == NULL) { + errno = EBADF; + return NULL; + } + + /* + * We know we're in the same conn context. So we + * can use the relative path. + */ + + return sys_acl_get_file(fsp->fsp_name, SMB_ACL_TYPE_ACCESS); +} +#endif + +int sys_acl_get_info(SMB_ACL_ENTRY_T entry, SMB_ACL_TAG_T *tag_type_p, uint32 *bits_p, id_t *u_g_id_p) +{ + *tag_type_p = entry->a_type; + + *bits_p = entry->a_perm; + + if (*tag_type_p == SMB_ACL_USER || *tag_type_p == SMB_ACL_GROUP) + *u_g_id_p = entry->a_id; + + return 0; +} + +SMB_ACL_T sys_acl_init(int count) +{ + SMB_ACL_T a; + + if (count < 0) { + errno = EINVAL; + return NULL; + } + + /* + * note that since the definition of the structure pointed + * to by the SMB_ACL_T includes the first element of the + * acl[] array, this actually allocates an ACL with room + * for (count+1) entries + */ + if ((a = (SMB_ACL_T)SMB_MALLOC(sizeof a[0] + count * sizeof(struct acl))) == NULL) { + errno = ENOMEM; + return NULL; + } + + a->size = count + 1; + a->count = 0; + a->next = -1; + + return a; +} + + +int sys_acl_create_entry(SMB_ACL_T *acl_p, SMB_ACL_ENTRY_T *entry_p) +{ + SMB_ACL_T acl_d; + SMB_ACL_ENTRY_T entry_d; + + if (acl_p == NULL || entry_p == NULL || (acl_d = *acl_p) == NULL) { + errno = EINVAL; + return -1; + } + + if (acl_d->count >= acl_d->size) { + errno = ENOSPC; + return -1; + } + + entry_d = &acl_d->acl[acl_d->count++]; + entry_d->a_type = 0; + entry_d->a_id = -1; + entry_d->a_perm = 0; + *entry_p = entry_d; + + return 0; +} + +int sys_acl_set_info(SMB_ACL_ENTRY_T entry, SMB_ACL_TAG_T tag_type, uint32 bits, id_t u_g_id) +{ + entry->a_type = tag_type; + + if (tag_type == SMB_ACL_USER || tag_type == SMB_ACL_GROUP) + entry->a_id = u_g_id; + + entry->a_perm = bits; + + return 0; +} + +int sys_acl_set_access_bits(SMB_ACL_ENTRY_T entry_d, uint32 bits) +{ + entry_d->a_perm = bits; + + return 0; +} + +/* Structure to capture the count for each type of ACE. */ + +struct hpux_acl_types { + int n_user; + int n_def_user; + int n_user_obj; + int n_def_user_obj; + + int n_group; + int n_def_group; + int n_group_obj; + int n_def_group_obj; + + int n_other; + int n_other_obj; + int n_def_other_obj; + + int n_class_obj; + int n_def_class_obj; + + int n_illegal_obj; +}; + +/* count_obj: + * Counts the different number of objects in a given array of ACL + * structures. + * Inputs: + * + * acl_count - Count of ACLs in the array of ACL strucutres. + * aclp - Array of ACL structures. + * acl_type_count - Pointer to acl_types structure. Should already be + * allocated. + * Output: + * + * acl_type_count - This structure is filled up with counts of various + * acl types. + */ + +static void hpux_count_obj(int acl_count, struct acl *aclp, struct hpux_acl_types *acl_type_count) +{ + int i; + + memset(acl_type_count, 0, sizeof(struct hpux_acl_types)); + + for(i=0;in_user++; + break; + case USER_OBJ: + acl_type_count->n_user_obj++; + break; + case DEF_USER_OBJ: + acl_type_count->n_def_user_obj++; + break; + case GROUP: + acl_type_count->n_group++; + break; + case GROUP_OBJ: + acl_type_count->n_group_obj++; + break; + case DEF_GROUP_OBJ: + acl_type_count->n_def_group_obj++; + break; + case OTHER_OBJ: + acl_type_count->n_other_obj++; + break; + case DEF_OTHER_OBJ: + acl_type_count->n_def_other_obj++; + break; + case CLASS_OBJ: + acl_type_count->n_class_obj++; + break; + case DEF_CLASS_OBJ: + acl_type_count->n_def_class_obj++; + break; + case DEF_USER: + acl_type_count->n_def_user++; + break; + case DEF_GROUP: + acl_type_count->n_def_group++; + break; + default: + acl_type_count->n_illegal_obj++; + break; + } + } +} + +/* swap_acl_entries: Swaps two ACL entries. + * + * Inputs: aclp0, aclp1 - ACL entries to be swapped. + */ + +static void hpux_swap_acl_entries(struct acl *aclp0, struct acl *aclp1) +{ + struct acl temp_acl; + + temp_acl.a_type = aclp0->a_type; + temp_acl.a_id = aclp0->a_id; + temp_acl.a_perm = aclp0->a_perm; + + aclp0->a_type = aclp1->a_type; + aclp0->a_id = aclp1->a_id; + aclp0->a_perm = aclp1->a_perm; + + aclp1->a_type = temp_acl.a_type; + aclp1->a_id = temp_acl.a_id; + aclp1->a_perm = temp_acl.a_perm; +} + +/* prohibited_duplicate_type + * Identifies if given ACL type can have duplicate entries or + * not. + * + * Inputs: acl_type - ACL Type. + * + * Outputs: + * + * Return.. + * + * True - If the ACL type matches any of the prohibited types. + * False - If the ACL type doesn't match any of the prohibited types. + */ + +static BOOL hpux_prohibited_duplicate_type(int acl_type) +{ + switch(acl_type) { + case USER: + case GROUP: + case DEF_USER: + case DEF_GROUP: + return True; + default: + return False; + } +} + +/* get_needed_class_perm + * Returns the permissions of a ACL structure only if the ACL + * type matches one of the pre-determined types for computing + * CLASS_OBJ permissions. + * + * Inputs: aclp - Pointer to ACL structure. + */ + +static int hpux_get_needed_class_perm(struct acl *aclp) +{ + switch(aclp->a_type) { + case USER: + case GROUP_OBJ: + case GROUP: + case DEF_USER_OBJ: + case DEF_USER: + case DEF_GROUP_OBJ: + case DEF_GROUP: + case DEF_CLASS_OBJ: + case DEF_OTHER_OBJ: + return aclp->a_perm; + default: + return 0; + } +} + +/* acl_sort for HPUX. + * Sorts the array of ACL structures as per the description in + * aclsort man page. Refer to aclsort man page for more details + * + * Inputs: + * + * acl_count - Count of ACLs in the array of ACL structures. + * calclass - If this is not zero, then we compute the CLASS_OBJ + * permissions. + * aclp - Array of ACL structures. + * + * Outputs: + * + * aclp - Sorted array of ACL structures. + * + * Outputs: + * + * Returns 0 for success -1 for failure. Prints a message to the Samba + * debug log in case of failure. + */ + +static int hpux_acl_sort(int acl_count, int calclass, struct acl *aclp) +{ +#if !defined(HAVE_HPUX_ACLSORT) + /* + * The aclsort() system call is availabe on the latest HPUX General + * Patch Bundles. So for HPUX, we developed our version of acl_sort + * function. Because, we don't want to update to a new + * HPUX GR bundle just for aclsort() call. + */ + + struct hpux_acl_types acl_obj_count; + int n_class_obj_perm = 0; + int i, j; + + if(!acl_count) { + DEBUG(10,("Zero acl count passed. Returning Success\n")); + return 0; + } + + if(aclp == NULL) { + DEBUG(0,("Null ACL pointer in hpux_acl_sort. Returning Failure. \n")); + return -1; + } + + /* Count different types of ACLs in the ACLs array */ + + hpux_count_obj(acl_count, aclp, &acl_obj_count); + + /* There should be only one entry each of type USER_OBJ, GROUP_OBJ, + * CLASS_OBJ and OTHER_OBJ + */ + + if( (acl_obj_count.n_user_obj != 1) || + (acl_obj_count.n_group_obj != 1) || + (acl_obj_count.n_class_obj != 1) || + (acl_obj_count.n_other_obj != 1) + ) { + DEBUG(0,("hpux_acl_sort: More than one entry or no entries for \ +USER OBJ or GROUP_OBJ or OTHER_OBJ or CLASS_OBJ\n")); + return -1; + } + + /* If any of the default objects are present, there should be only + * one of them each. + */ + + if( (acl_obj_count.n_def_user_obj > 1) || (acl_obj_count.n_def_group_obj > 1) || + (acl_obj_count.n_def_other_obj > 1) || (acl_obj_count.n_def_class_obj > 1) ) { + DEBUG(0,("hpux_acl_sort: More than one entry for DEF_CLASS_OBJ \ +or DEF_USER_OBJ or DEF_GROUP_OBJ or DEF_OTHER_OBJ\n")); + return -1; + } + + /* We now have proper number of OBJ and DEF_OBJ entries. Now sort the acl + * structures. + * + * Sorting crieteria - First sort by ACL type. If there are multiple entries of + * same ACL type, sort by ACL id. + * + * I am using the trival kind of sorting method here because, performance isn't + * really effected by the ACLs feature. More over there aren't going to be more + * than 17 entries on HPUX. + */ + + for(i=0; i aclp[j].a_type ) { + /* ACL entries out of order, swap them */ + + hpux_swap_acl_entries((aclp+i), (aclp+j)); + + } else if ( aclp[i].a_type == aclp[j].a_type ) { + + /* ACL entries of same type, sort by id */ + + if(aclp[i].a_id > aclp[j].a_id) { + hpux_swap_acl_entries((aclp+i), (aclp+j)); + } else if (aclp[i].a_id == aclp[j].a_id) { + /* We have a duplicate entry. */ + if(hpux_prohibited_duplicate_type(aclp[i].a_type)) { + DEBUG(0, ("hpux_acl_sort: Duplicate entry: Type(hex): %x Id: %d\n", + aclp[i].a_type, aclp[i].a_id)); + return -1; + } + } + + } + } + } + + /* set the class obj permissions to the computed one. */ + if(calclass) { + int n_class_obj_index = -1; + + for(i=0;icount <= 4); + + if (hpux_acl_sort(acl_d->count, fixmask, acl_d->acl) != 0) { + errno = EINVAL; + return -1; + } + return 0; +} + +int sys_acl_valid(SMB_ACL_T acl_d) +{ + return acl_sort(acl_d); +} + +int sys_acl_set_file(const char *name, SMB_ACL_TYPE_T type, SMB_ACL_T acl_d) +{ + struct stat s; + struct acl *acl_p; + int acl_count; + struct acl *acl_buf = NULL; + int ret; + + if(hpux_acl_call_presence() == False) { + /* Looks like we don't have the acl() system call on HPUX. + * May be the system doesn't have the latest version of JFS. + */ + errno=ENOSYS; + return -1; + } + + if (type != SMB_ACL_TYPE_ACCESS && type != SMB_ACL_TYPE_DEFAULT) { + errno = EINVAL; + return -1; + } + + if (acl_sort(acl_d) != 0) { + return -1; + } + + acl_p = &acl_d->acl[0]; + acl_count = acl_d->count; + + /* + * if it's a directory there is extra work to do + * since the acl() system call will replace both + * the access ACLs and the default ACLs (if any) + */ + if (stat(name, &s) != 0) { + return -1; + } + if (S_ISDIR(s.st_mode)) { + SMB_ACL_T acc_acl; + SMB_ACL_T def_acl; + SMB_ACL_T tmp_acl; + int i; + + if (type == SMB_ACL_TYPE_ACCESS) { + acc_acl = acl_d; + def_acl = tmp_acl = sys_acl_get_file(name, SMB_ACL_TYPE_DEFAULT); + + } else { + def_acl = acl_d; + acc_acl = tmp_acl = sys_acl_get_file(name, SMB_ACL_TYPE_ACCESS); + } + + if (tmp_acl == NULL) { + return -1; + } + + /* + * allocate a temporary buffer for the complete ACL + */ + acl_count = acc_acl->count + def_acl->count; + acl_p = acl_buf = SMB_MALLOC_ARRAY(struct acl, acl_count); + + if (acl_buf == NULL) { + sys_acl_free_acl(tmp_acl); + errno = ENOMEM; + return -1; + } + + /* + * copy the access control and default entries into the buffer + */ + memcpy(&acl_buf[0], &acc_acl->acl[0], + acc_acl->count * sizeof(acl_buf[0])); + + memcpy(&acl_buf[acc_acl->count], &def_acl->acl[0], + def_acl->count * sizeof(acl_buf[0])); + + /* + * set the ACL_DEFAULT flag on the default entries + */ + for (i = acc_acl->count; i < acl_count; i++) { + acl_buf[i].a_type |= ACL_DEFAULT; + } + + sys_acl_free_acl(tmp_acl); + + } else if (type != SMB_ACL_TYPE_ACCESS) { + errno = EINVAL; + return -1; + } + + ret = acl(name, ACL_SET, acl_count, acl_p); + + if (acl_buf) { + free(acl_buf); + } + + return ret; +} + +#if 0 +int sys_acl_set_fd(int fd, SMB_ACL_T acl_d) +{ + /* + * HPUX doesn't have the facl call. Fake it using the path.... JRA. + */ + + files_struct *fsp = file_find_fd(fd); + + if (fsp == NULL) { + errno = EBADF; + return NULL; + } + + if (acl_sort(acl_d) != 0) { + return -1; + } + + /* + * We know we're in the same conn context. So we + * can use the relative path. + */ + + return sys_acl_set_file(fsp->fsp_name, SMB_ACL_TYPE_ACCESS, acl_d); +} +#endif + +int sys_acl_delete_def_file(const char *path) +{ + SMB_ACL_T acl_d; + int ret; + + /* + * fetching the access ACL and rewriting it has + * the effect of deleting the default ACL + */ + if ((acl_d = sys_acl_get_file(path, SMB_ACL_TYPE_ACCESS)) == NULL) { + return -1; + } + + ret = acl(path, ACL_SET, acl_d->count, acl_d->acl); + + sys_acl_free_acl(acl_d); + + return ret; +} + +int sys_acl_free_acl(SMB_ACL_T acl_d) +{ + free(acl_d); + return 0; +} + +#elif defined(HAVE_IRIX_ACLS) /*---------------------------------------------*/ + +int sys_acl_get_entry(SMB_ACL_T acl_d, int entry_id, SMB_ACL_ENTRY_T *entry_p) +{ + if (entry_id != SMB_ACL_FIRST_ENTRY && entry_id != SMB_ACL_NEXT_ENTRY) { + errno = EINVAL; + return -1; + } + + if (entry_p == NULL) { + errno = EINVAL; + return -1; + } + + if (entry_id == SMB_ACL_FIRST_ENTRY) { + acl_d->next = 0; + } + + if (acl_d->next < 0) { + errno = EINVAL; + return -1; + } + + if (acl_d->next >= acl_d->aclp->acl_cnt) { + return 0; + } + + *entry_p = &acl_d->aclp->acl_entry[acl_d->next++]; + + return 1; +} + +int sys_acl_get_tag_type(SMB_ACL_ENTRY_T entry_d, SMB_ACL_TAG_T *type_p) +{ + *type_p = entry_d->ae_tag; + + return 0; +} + +SMB_ACL_T sys_acl_get_file(const char *path_p, SMB_ACL_TYPE_T type) +{ + SMB_ACL_T a; + + if ((a = SMB_MALLOC_P(struct SMB_ACL_T)) == NULL) { + errno = ENOMEM; + return NULL; + } + if ((a->aclp = acl_get_file(path_p, type)) == NULL) { + SAFE_FREE(a); + return NULL; + } + a->next = -1; + a->freeaclp = True; + return a; +} + +#if 0 +SMB_ACL_T sys_acl_get_fd(int fd) +{ + SMB_ACL_T a; + + if ((a = SMB_MALLOC_P(struct SMB_ACL_T)) == NULL) { + errno = ENOMEM; + return NULL; + } + if ((a->aclp = acl_get_fd(fd)) == NULL) { + SAFE_FREE(a); + return NULL; + } + a->next = -1; + a->freeaclp = True; + return a; +} +#endif + +int sys_acl_get_info(SMB_ACL_ENTRY_T entry, SMB_ACL_TAG_T *tag_type_p, uint32 *bits_p, id_t *u_g_id_p) +{ + *tag_type_p = entry->ae_tag; + + *bits_p = entry->ae_perm; + + if (*tag_type_p == SMB_ACL_USER || *tag_type_p == SMB_ACL_GROUP) + *u_g_id_p = entry->ae_id; + + return 0; +} + +SMB_ACL_T sys_acl_init(int count) +{ + SMB_ACL_T a; + + if (count < 0) { + errno = EINVAL; + return NULL; + } + + if ((a = (SMB_ACL_T)SMB_MALLOC(sizeof a[0] + sizeof (struct acl))) == NULL) { + errno = ENOMEM; + return NULL; + } + + a->next = -1; + a->freeaclp = False; + a->aclp = (struct acl *)((char *)a + sizeof a[0]); + a->aclp->acl_cnt = 0; + + return a; +} + + +int sys_acl_create_entry(SMB_ACL_T *acl_p, SMB_ACL_ENTRY_T *entry_p) +{ + SMB_ACL_T acl_d; + SMB_ACL_ENTRY_T entry_d; + + if (acl_p == NULL || entry_p == NULL || (acl_d = *acl_p) == NULL) { + errno = EINVAL; + return -1; + } + + if (acl_d->aclp->acl_cnt >= ACL_MAX_ENTRIES) { + errno = ENOSPC; + return -1; + } + + entry_d = &acl_d->aclp->acl_entry[acl_d->aclp->acl_cnt++]; + entry_d->ae_tag = 0; + entry_d->ae_id = 0; + entry_d->ae_perm = 0; + *entry_p = entry_d; + + return 0; +} + +int sys_acl_set_info(SMB_ACL_ENTRY_T entry, SMB_ACL_TAG_T tag_type, uint32 bits, id_t u_g_id) +{ + entry->ae_tag = tag_type; + + if (tag_type == SMB_ACL_USER || tag_type == SMB_ACL_GROUP) + entry->ae_id = u_g_id; + + entry->ae_perm = bits; + + return 0; +} + +int sys_acl_set_access_bits(SMB_ACL_ENTRY_T entry_d, uint32 bits) +{ + entry_d->ae_perm = bits; + + return 0; +} + +int sys_acl_valid(SMB_ACL_T acl_d) +{ + return acl_valid(acl_d->aclp); +} + +int sys_acl_set_file(const char *name, SMB_ACL_TYPE_T type, SMB_ACL_T acl_d) +{ + return acl_set_file(name, type, acl_d->aclp); +} + +#if 0 +int sys_acl_set_fd(int fd, SMB_ACL_T acl_d) +{ + return acl_set_fd(fd, acl_d->aclp); +} +#endif + +int sys_acl_delete_def_file(const char *name) +{ + return acl_delete_def_file(name); +} + +int sys_acl_free_acl(SMB_ACL_T acl_d) +{ + if (acl_d->freeaclp) { + acl_free(acl_d->aclp); + } + acl_free(acl_d); + return 0; +} + +#elif defined(HAVE_AIX_ACLS) /*----------------------------------------------*/ + +/* Donated by Medha Date, mdate@austin.ibm.com, for IBM */ + +int sys_acl_get_entry( SMB_ACL_T theacl, int entry_id, SMB_ACL_ENTRY_T *entry_p) +{ + struct acl_entry_link *link; + struct new_acl_entry *entry; + int keep_going; + + if (entry_id == SMB_ACL_FIRST_ENTRY) + theacl->count = 0; + else if (entry_id != SMB_ACL_NEXT_ENTRY) { + errno = EINVAL; + return -1; + } + + DEBUG(10,("This is the count: %d\n",theacl->count)); + + /* Check if count was previously set to -1. * + * If it was, that means we reached the end * + * of the acl last time. */ + if(theacl->count == -1) + return(0); + + link = theacl; + /* To get to the next acl, traverse linked list until index * + * of acl matches the count we are keeping. This count is * + * incremented each time we return an acl entry. */ + + for(keep_going = 0; keep_going < theacl->count; keep_going++) + link = link->nextp; + + entry = *entry_p = link->entryp; + + DEBUG(10,("*entry_p is %d\n",entry_p)); + DEBUG(10,("*entry_p->ace_access is %d\n",entry->ace_access)); + + /* Increment count */ + theacl->count++; + if(link->nextp == NULL) + theacl->count = -1; + + return(1); +} + +int sys_acl_get_tag_type( SMB_ACL_ENTRY_T entry_d, SMB_ACL_TAG_T *tag_type_p) +{ + /* Initialize tag type */ + + *tag_type_p = -1; + DEBUG(10,("the tagtype is %d\n",entry_d->ace_id->id_type)); + + /* Depending on what type of entry we have, * + * return tag type. */ + switch(entry_d->ace_id->id_type) { + case ACEID_USER: + *tag_type_p = SMB_ACL_USER; + break; + case ACEID_GROUP: + *tag_type_p = SMB_ACL_GROUP; + break; + + case SMB_ACL_USER_OBJ: + case SMB_ACL_GROUP_OBJ: + case SMB_ACL_OTHER: + *tag_type_p = entry_d->ace_id->id_type; + break; + + default: + return(-1); + } + + return(0); +} + +SMB_ACL_T sys_acl_get_file( const char *path_p, SMB_ACL_TYPE_T type) +{ + struct acl *file_acl = (struct acl *)NULL; + struct acl_entry *acl_entry; + struct new_acl_entry *new_acl_entry; + struct ace_id *idp; + struct acl_entry_link *acl_entry_link; + struct acl_entry_link *acl_entry_link_head; + int i; + int rc = 0; + + /* AIX has no DEFAULT */ + if ( type == SMB_ACL_TYPE_DEFAULT ) { +#ifdef ENOTSUP + errno = ENOTSUP; +#else + errno = ENOSYS; +#endif + return NULL; + } + + /* Get the acl using statacl */ + + DEBUG(10,("Entering sys_acl_get_file\n")); + DEBUG(10,("path_p is %s\n",path_p)); + + file_acl = (struct acl *)SMB_MALLOC(BUFSIZ); + + if(file_acl == NULL) { + errno=ENOMEM; + DEBUG(0,("Error in AIX sys_acl_get_file: %d\n",errno)); + return(NULL); + } + + memset(file_acl,0,BUFSIZ); + + rc = statacl((char *)path_p,0,file_acl,BUFSIZ); + if(rc == -1) { + DEBUG(0,("statacl returned %d with errno %d\n",rc,errno)); + SAFE_FREE(file_acl); + return(NULL); + } + + DEBUG(10,("Got facl and returned it\n")); + + /* Point to the first acl entry in the acl */ + acl_entry = file_acl->acl_ext; + + /* Begin setting up the head of the linked list * + * that will be used for the storing the acl * + * in a way that is useful for the posix_acls.c * + * code. */ + + acl_entry_link_head = acl_entry_link = sys_acl_init(0); + if(acl_entry_link_head == NULL) + return(NULL); + + acl_entry_link->entryp = SMB_MALLOC_P(struct new_acl_entry); + if(acl_entry_link->entryp == NULL) { + SAFE_FREE(file_acl); + errno = ENOMEM; + DEBUG(0,("Error in AIX sys_acl_get_file is %d\n",errno)); + return(NULL); + } + + DEBUG(10,("acl_entry is %d\n",acl_entry)); + DEBUG(10,("acl_last(file_acl) id %d\n",acl_last(file_acl))); + + /* Check if the extended acl bit is on. * + * If it isn't, do not show the * + * contents of the acl since AIX intends * + * the extended info to remain unused */ + + if(file_acl->acl_mode & S_IXACL){ + /* while we are not pointing to the very end */ + while(acl_entry < acl_last(file_acl)) { + /* before we malloc anything, make sure this is */ + /* a valid acl entry and one that we want to map */ + idp = id_nxt(acl_entry->ace_id); + if((acl_entry->ace_type == ACC_SPECIFY || + (acl_entry->ace_type == ACC_PERMIT)) && (idp != id_last(acl_entry))) { + acl_entry = acl_nxt(acl_entry); + continue; + } + + idp = acl_entry->ace_id; + + /* Check if this is the first entry in the linked list. * + * The first entry needs to keep prevp pointing to NULL * + * and already has entryp allocated. */ + + if(acl_entry_link_head->count != 0) { + acl_entry_link->nextp = SMB_MALLOC_P(struct acl_entry_link); + + if(acl_entry_link->nextp == NULL) { + SAFE_FREE(file_acl); + errno = ENOMEM; + DEBUG(0,("Error in AIX sys_acl_get_file is %d\n",errno)); + return(NULL); + } + + acl_entry_link->nextp->prevp = acl_entry_link; + acl_entry_link = acl_entry_link->nextp; + acl_entry_link->entryp = SMB_MALLOC_P(struct new_acl_entry); + if(acl_entry_link->entryp == NULL) { + SAFE_FREE(file_acl); + errno = ENOMEM; + DEBUG(0,("Error in AIX sys_acl_get_file is %d\n",errno)); + return(NULL); + } + acl_entry_link->nextp = NULL; + } + + acl_entry_link->entryp->ace_len = acl_entry->ace_len; + + /* Don't really need this since all types are going * + * to be specified but, it's better than leaving it 0 */ + + acl_entry_link->entryp->ace_type = acl_entry->ace_type; + + acl_entry_link->entryp->ace_access = acl_entry->ace_access; + + memcpy(acl_entry_link->entryp->ace_id,idp,sizeof(struct ace_id)); + + /* The access in the acl entries must be left shifted by * + * three bites, because they will ultimately be compared * + * to S_IRUSR, S_IWUSR, and S_IXUSR. */ + + switch(acl_entry->ace_type){ + case ACC_PERMIT: + case ACC_SPECIFY: + acl_entry_link->entryp->ace_access = acl_entry->ace_access; + acl_entry_link->entryp->ace_access <<= 6; + acl_entry_link_head->count++; + break; + case ACC_DENY: + /* Since there is no way to return a DENY acl entry * + * change to PERMIT and then shift. */ + DEBUG(10,("acl_entry->ace_access is %d\n",acl_entry->ace_access)); + acl_entry_link->entryp->ace_access = ~acl_entry->ace_access & 7; + DEBUG(10,("acl_entry_link->entryp->ace_access is %d\n",acl_entry_link->entryp->ace_access)); + acl_entry_link->entryp->ace_access <<= 6; + acl_entry_link_head->count++; + break; + default: + return(0); + } + + DEBUG(10,("acl_entry = %d\n",acl_entry)); + DEBUG(10,("The ace_type is %d\n",acl_entry->ace_type)); + + acl_entry = acl_nxt(acl_entry); + } + } /* end of if enabled */ + + /* Since owner, group, other acl entries are not * + * part of the acl entries in an acl, they must * + * be dummied up to become part of the list. */ + + for( i = 1; i < 4; i++) { + DEBUG(10,("i is %d\n",i)); + if(acl_entry_link_head->count != 0) { + acl_entry_link->nextp = SMB_MALLOC_P(struct acl_entry_link); + if(acl_entry_link->nextp == NULL) { + SAFE_FREE(file_acl); + errno = ENOMEM; + DEBUG(0,("Error in AIX sys_acl_get_file is %d\n",errno)); + return(NULL); + } + + acl_entry_link->nextp->prevp = acl_entry_link; + acl_entry_link = acl_entry_link->nextp; + acl_entry_link->entryp = SMB_MALLOC_P(struct new_acl_entry); + if(acl_entry_link->entryp == NULL) { + SAFE_FREE(file_acl); + errno = ENOMEM; + DEBUG(0,("Error in AIX sys_acl_get_file is %d\n",errno)); + return(NULL); + } + } + + acl_entry_link->nextp = NULL; + + new_acl_entry = acl_entry_link->entryp; + idp = new_acl_entry->ace_id; + + new_acl_entry->ace_len = sizeof(struct acl_entry); + new_acl_entry->ace_type = ACC_PERMIT; + idp->id_len = sizeof(struct ace_id); + DEBUG(10,("idp->id_len = %d\n",idp->id_len)); + memset(idp->id_data,0,sizeof(uid_t)); + + switch(i) { + case 2: + new_acl_entry->ace_access = file_acl->g_access << 6; + idp->id_type = SMB_ACL_GROUP_OBJ; + break; + + case 3: + new_acl_entry->ace_access = file_acl->o_access << 6; + idp->id_type = SMB_ACL_OTHER; + break; + + case 1: + new_acl_entry->ace_access = file_acl->u_access << 6; + idp->id_type = SMB_ACL_USER_OBJ; + break; + + default: + return(NULL); + + } + + acl_entry_link_head->count++; + DEBUG(10,("new_acl_entry->ace_access = %d\n",new_acl_entry->ace_access)); + } + + acl_entry_link_head->count = 0; + SAFE_FREE(file_acl); + + return(acl_entry_link_head); +} + +#if 0 +SMB_ACL_T sys_acl_get_fd(int fd) +{ + struct acl *file_acl = (struct acl *)NULL; + struct acl_entry *acl_entry; + struct new_acl_entry *new_acl_entry; + struct ace_id *idp; + struct acl_entry_link *acl_entry_link; + struct acl_entry_link *acl_entry_link_head; + int i; + int rc = 0; + + /* Get the acl using fstatacl */ + + DEBUG(10,("Entering sys_acl_get_fd\n")); + DEBUG(10,("fd is %d\n",fd)); + file_acl = (struct acl *)SMB_MALLOC(BUFSIZ); + + if(file_acl == NULL) { + errno=ENOMEM; + DEBUG(0,("Error in sys_acl_get_fd is %d\n",errno)); + return(NULL); + } + + memset(file_acl,0,BUFSIZ); + + rc = fstatacl(fd,0,file_acl,BUFSIZ); + if(rc == -1) { + DEBUG(0,("The fstatacl call returned %d with errno %d\n",rc,errno)); + SAFE_FREE(file_acl); + return(NULL); + } + + DEBUG(10,("Got facl and returned it\n")); + + /* Point to the first acl entry in the acl */ + + acl_entry = file_acl->acl_ext; + /* Begin setting up the head of the linked list * + * that will be used for the storing the acl * + * in a way that is useful for the posix_acls.c * + * code. */ + + acl_entry_link_head = acl_entry_link = sys_acl_init(0); + if(acl_entry_link_head == NULL){ + SAFE_FREE(file_acl); + return(NULL); + } + + acl_entry_link->entryp = SMB_MALLOC_P(struct new_acl_entry); + + if(acl_entry_link->entryp == NULL) { + errno = ENOMEM; + DEBUG(0,("Error in sys_acl_get_fd is %d\n",errno)); + SAFE_FREE(file_acl); + return(NULL); + } + + DEBUG(10,("acl_entry is %d\n",acl_entry)); + DEBUG(10,("acl_last(file_acl) id %d\n",acl_last(file_acl))); + + /* Check if the extended acl bit is on. * + * If it isn't, do not show the * + * contents of the acl since AIX intends * + * the extended info to remain unused */ + + if(file_acl->acl_mode & S_IXACL){ + /* while we are not pointing to the very end */ + while(acl_entry < acl_last(file_acl)) { + /* before we malloc anything, make sure this is */ + /* a valid acl entry and one that we want to map */ + + idp = id_nxt(acl_entry->ace_id); + if((acl_entry->ace_type == ACC_SPECIFY || + (acl_entry->ace_type == ACC_PERMIT)) && (idp != id_last(acl_entry))) { + acl_entry = acl_nxt(acl_entry); + continue; + } + + idp = acl_entry->ace_id; + + /* Check if this is the first entry in the linked list. * + * The first entry needs to keep prevp pointing to NULL * + * and already has entryp allocated. */ + + if(acl_entry_link_head->count != 0) { + acl_entry_link->nextp = SMB_MALLOC_P(struct acl_entry_link); + if(acl_entry_link->nextp == NULL) { + errno = ENOMEM; + DEBUG(0,("Error in sys_acl_get_fd is %d\n",errno)); + SAFE_FREE(file_acl); + return(NULL); + } + acl_entry_link->nextp->prevp = acl_entry_link; + acl_entry_link = acl_entry_link->nextp; + acl_entry_link->entryp = SMB_MALLOC_P(struct new_acl_entry); + if(acl_entry_link->entryp == NULL) { + errno = ENOMEM; + DEBUG(0,("Error in sys_acl_get_fd is %d\n",errno)); + SAFE_FREE(file_acl); + return(NULL); + } + + acl_entry_link->nextp = NULL; + } + + acl_entry_link->entryp->ace_len = acl_entry->ace_len; + + /* Don't really need this since all types are going * + * to be specified but, it's better than leaving it 0 */ + + acl_entry_link->entryp->ace_type = acl_entry->ace_type; + acl_entry_link->entryp->ace_access = acl_entry->ace_access; + + memcpy(acl_entry_link->entryp->ace_id, idp, sizeof(struct ace_id)); + + /* The access in the acl entries must be left shifted by * + * three bites, because they will ultimately be compared * + * to S_IRUSR, S_IWUSR, and S_IXUSR. */ + + switch(acl_entry->ace_type){ + case ACC_PERMIT: + case ACC_SPECIFY: + acl_entry_link->entryp->ace_access = acl_entry->ace_access; + acl_entry_link->entryp->ace_access <<= 6; + acl_entry_link_head->count++; + break; + case ACC_DENY: + /* Since there is no way to return a DENY acl entry * + * change to PERMIT and then shift. */ + DEBUG(10,("acl_entry->ace_access is %d\n",acl_entry->ace_access)); + acl_entry_link->entryp->ace_access = ~acl_entry->ace_access & 7; + DEBUG(10,("acl_entry_link->entryp->ace_access is %d\n",acl_entry_link->entryp->ace_access)); + acl_entry_link->entryp->ace_access <<= 6; + acl_entry_link_head->count++; + break; + default: + return(0); + } + + DEBUG(10,("acl_entry = %d\n",acl_entry)); + DEBUG(10,("The ace_type is %d\n",acl_entry->ace_type)); + + acl_entry = acl_nxt(acl_entry); + } + } /* end of if enabled */ + + /* Since owner, group, other acl entries are not * + * part of the acl entries in an acl, they must * + * be dummied up to become part of the list. */ + + for( i = 1; i < 4; i++) { + DEBUG(10,("i is %d\n",i)); + if(acl_entry_link_head->count != 0){ + acl_entry_link->nextp = SMB_MALLOC_P(struct acl_entry_link); + if(acl_entry_link->nextp == NULL) { + errno = ENOMEM; + DEBUG(0,("Error in sys_acl_get_fd is %d\n",errno)); + SAFE_FREE(file_acl); + return(NULL); + } + + acl_entry_link->nextp->prevp = acl_entry_link; + acl_entry_link = acl_entry_link->nextp; + acl_entry_link->entryp = SMB_MALLOC_P(struct new_acl_entry); + + if(acl_entry_link->entryp == NULL) { + SAFE_FREE(file_acl); + errno = ENOMEM; + DEBUG(0,("Error in sys_acl_get_fd is %d\n",errno)); + return(NULL); + } + } + + acl_entry_link->nextp = NULL; + + new_acl_entry = acl_entry_link->entryp; + idp = new_acl_entry->ace_id; + + new_acl_entry->ace_len = sizeof(struct acl_entry); + new_acl_entry->ace_type = ACC_PERMIT; + idp->id_len = sizeof(struct ace_id); + DEBUG(10,("idp->id_len = %d\n",idp->id_len)); + memset(idp->id_data,0,sizeof(uid_t)); + + switch(i) { + case 2: + new_acl_entry->ace_access = file_acl->g_access << 6; + idp->id_type = SMB_ACL_GROUP_OBJ; + break; + + case 3: + new_acl_entry->ace_access = file_acl->o_access << 6; + idp->id_type = SMB_ACL_OTHER; + break; + + case 1: + new_acl_entry->ace_access = file_acl->u_access << 6; + idp->id_type = SMB_ACL_USER_OBJ; + break; + + default: + return(NULL); + } + + acl_entry_link_head->count++; + DEBUG(10,("new_acl_entry->ace_access = %d\n",new_acl_entry->ace_access)); + } + + acl_entry_link_head->count = 0; + SAFE_FREE(file_acl); + + return(acl_entry_link_head); +} +#endif + +int sys_acl_get_info(SMB_ACL_ENTRY_T entry, SMB_ACL_TAG_T *tag_type_p, uint32 *bits_p, id_t *u_g_id_p) +{ + uint *permset; + + if (sys_acl_get_tag_type(entry, tag_type_p) != 0) + return -1; + + if (*tag_type_p == SMB_ACL_USER || *tag_type_p == SMB_ACL_GROUP) + memcpy(u_g_id_p, entry->ace_id->id_data, sizeof (id_t)); + + permset = &entry->ace_access; + + DEBUG(10,("*permset is %d\n",*permset)); + *bits_p = (*permset & S_IRUSR ? 4 : 0) + | (*permset & S_IWUSR ? 2 : 0) + | (*permset & S_IXUSR ? 1 : 0); + + return 0; +} + +SMB_ACL_T sys_acl_init( int count) +{ + struct acl_entry_link *theacl = NULL; + + if (count < 0) { + errno = EINVAL; + return NULL; + } + + DEBUG(10,("Entering sys_acl_init\n")); + + theacl = SMB_MALLOC_P(struct acl_entry_link); + if(theacl == NULL) { + errno = ENOMEM; + DEBUG(0,("Error in sys_acl_init is %d\n",errno)); + return(NULL); + } + + theacl->count = 0; + theacl->nextp = NULL; + theacl->prevp = NULL; + theacl->entryp = NULL; + DEBUG(10,("Exiting sys_acl_init\n")); + return(theacl); +} + +int sys_acl_create_entry( SMB_ACL_T *pacl, SMB_ACL_ENTRY_T *pentry) +{ + struct acl_entry_link *theacl; + struct acl_entry_link *acl_entryp; + struct acl_entry_link *temp_entry; + int counting; + + DEBUG(10,("Entering the sys_acl_create_entry\n")); + + theacl = acl_entryp = *pacl; + + /* Get to the end of the acl before adding entry */ + + for(counting=0; counting < theacl->count; counting++){ + DEBUG(10,("The acl_entryp is %d\n",acl_entryp)); + temp_entry = acl_entryp; + acl_entryp = acl_entryp->nextp; + } + + if(theacl->count != 0){ + temp_entry->nextp = acl_entryp = SMB_MALLOC_P(struct acl_entry_link); + if(acl_entryp == NULL) { + errno = ENOMEM; + DEBUG(0,("Error in sys_acl_create_entry is %d\n",errno)); + return(-1); + } + + DEBUG(10,("The acl_entryp is %d\n",acl_entryp)); + acl_entryp->prevp = temp_entry; + DEBUG(10,("The acl_entryp->prevp is %d\n",acl_entryp->prevp)); + } + + *pentry = acl_entryp->entryp = SMB_MALLOC_P(struct new_acl_entry); + if(*pentry == NULL) { + errno = ENOMEM; + DEBUG(0,("Error in sys_acl_create_entry is %d\n",errno)); + return(-1); + } + + memset(*pentry,0,sizeof(struct new_acl_entry)); + acl_entryp->entryp->ace_len = sizeof(struct acl_entry); + acl_entryp->entryp->ace_type = ACC_PERMIT; + acl_entryp->entryp->ace_id->id_len = sizeof(struct ace_id); + acl_entryp->nextp = NULL; + theacl->count++; + DEBUG(10,("Exiting sys_acl_create_entry\n")); + return(0); +} + +int sys_acl_set_info(SMB_ACL_ENTRY_T entry, SMB_ACL_TAG_T tag_type, uint32 bits, id_t u_g_id) +{ + entry->ace_id->id_type = tag_type; + DEBUG(10,("The tag type is %d\n",entry->ace_id->id_type)); + + if (tag_type == SMB_ACL_USER || tag_type == SMB_ACL_GROUP) + memcpy(entry->ace_id->id_data, &u_g_id, sizeof (id_t)); + + entry->ace_access = bits; + DEBUG(10,("entry->ace_access = %d\n",entry->ace_access)); + + return 0; +} + +int sys_acl_set_access_bits(SMB_ACL_ENTRY_T entry, uint32 bits) +{ + DEBUG(10,("Starting AIX sys_acl_set_permset\n")); + entry->ace_access = bits; + DEBUG(10,("entry->ace_access = %d\n",entry->ace_access)); + DEBUG(10,("Ending AIX sys_acl_set_permset\n")); + return(0); +} + +int sys_acl_valid( SMB_ACL_T theacl ) +{ + int user_obj = 0; + int group_obj = 0; + int other_obj = 0; + struct acl_entry_link *acl_entry; + + for(acl_entry=theacl; acl_entry != NULL; acl_entry = acl_entry->nextp) { + user_obj += (acl_entry->entryp->ace_id->id_type == SMB_ACL_USER_OBJ); + group_obj += (acl_entry->entryp->ace_id->id_type == SMB_ACL_GROUP_OBJ); + other_obj += (acl_entry->entryp->ace_id->id_type == SMB_ACL_OTHER); + } + + DEBUG(10,("user_obj=%d, group_obj=%d, other_obj=%d\n",user_obj,group_obj,other_obj)); + + if(user_obj != 1 || group_obj != 1 || other_obj != 1) + return(-1); + + return(0); +} + +int sys_acl_set_file( const char *name, SMB_ACL_TYPE_T acltype, SMB_ACL_T theacl) +{ + struct acl_entry_link *acl_entry_link = NULL; + struct acl *file_acl = NULL; + struct acl *file_acl_temp = NULL; + struct acl_entry *acl_entry = NULL; + struct ace_id *ace_id = NULL; + uint id_type; + uint user_id; + uint acl_length; + uint rc; + + DEBUG(10,("Entering sys_acl_set_file\n")); + DEBUG(10,("File name is %s\n",name)); + + /* AIX has no default ACL */ + if(acltype == SMB_ACL_TYPE_DEFAULT) + return(0); + + acl_length = BUFSIZ; + file_acl = (struct acl *)SMB_MALLOC(BUFSIZ); + + if(file_acl == NULL) { + errno = ENOMEM; + DEBUG(0,("Error in sys_acl_set_file is %d\n",errno)); + return(-1); + } + + memset(file_acl,0,BUFSIZ); + + file_acl->acl_len = ACL_SIZ; + file_acl->acl_mode = S_IXACL; + + for(acl_entry_link=theacl; acl_entry_link != NULL; acl_entry_link = acl_entry_link->nextp) { + acl_entry_link->entryp->ace_access >>= 6; + id_type = acl_entry_link->entryp->ace_id->id_type; + + switch(id_type) { + case SMB_ACL_USER_OBJ: + file_acl->u_access = acl_entry_link->entryp->ace_access; + continue; + case SMB_ACL_GROUP_OBJ: + file_acl->g_access = acl_entry_link->entryp->ace_access; + continue; + case SMB_ACL_OTHER: + file_acl->o_access = acl_entry_link->entryp->ace_access; + continue; + case SMB_ACL_MASK: + continue; + } + + if((file_acl->acl_len + sizeof(struct acl_entry)) > acl_length) { + acl_length += sizeof(struct acl_entry); + file_acl_temp = (struct acl *)SMB_MALLOC(acl_length); + if(file_acl_temp == NULL) { + SAFE_FREE(file_acl); + errno = ENOMEM; + DEBUG(0,("Error in sys_acl_set_file is %d\n",errno)); + return(-1); + } + + memcpy(file_acl_temp,file_acl,file_acl->acl_len); + SAFE_FREE(file_acl); + file_acl = file_acl_temp; + } + + acl_entry = (struct acl_entry *)((char *)file_acl + file_acl->acl_len); + file_acl->acl_len += sizeof(struct acl_entry); + acl_entry->ace_len = acl_entry_link->entryp->ace_len; + acl_entry->ace_access = acl_entry_link->entryp->ace_access; + + /* In order to use this, we'll need to wait until we can get denies */ + /* if(!acl_entry->ace_access && acl_entry->ace_type == ACC_PERMIT) + acl_entry->ace_type = ACC_SPECIFY; */ + + acl_entry->ace_type = ACC_SPECIFY; + + ace_id = acl_entry->ace_id; + + ace_id->id_type = acl_entry_link->entryp->ace_id->id_type; + DEBUG(10,("The id type is %d\n",ace_id->id_type)); + ace_id->id_len = acl_entry_link->entryp->ace_id->id_len; + memcpy(&user_id, acl_entry_link->entryp->ace_id->id_data, sizeof(uid_t)); + memcpy(acl_entry->ace_id->id_data, &user_id, sizeof(uid_t)); + } + + rc = chacl((char*)name,file_acl,file_acl->acl_len); + DEBUG(10,("errno is %d\n",errno)); + DEBUG(10,("return code is %d\n",rc)); + SAFE_FREE(file_acl); + DEBUG(10,("Exiting the sys_acl_set_file\n")); + return(rc); +} + +#if 0 +int sys_acl_set_fd( int fd, SMB_ACL_T theacl) +{ + struct acl_entry_link *acl_entry_link = NULL; + struct acl *file_acl = NULL; + struct acl *file_acl_temp = NULL; + struct acl_entry *acl_entry = NULL; + struct ace_id *ace_id = NULL; + uint id_type; + uint user_id; + uint acl_length; + uint rc; + + DEBUG(10,("Entering sys_acl_set_fd\n")); + acl_length = BUFSIZ; + file_acl = (struct acl *)SMB_MALLOC(BUFSIZ); + + if(file_acl == NULL) { + errno = ENOMEM; + DEBUG(0,("Error in sys_acl_set_fd is %d\n",errno)); + return(-1); + } + + memset(file_acl,0,BUFSIZ); + + file_acl->acl_len = ACL_SIZ; + file_acl->acl_mode = S_IXACL; + + for(acl_entry_link=theacl; acl_entry_link != NULL; acl_entry_link = acl_entry_link->nextp) { + acl_entry_link->entryp->ace_access >>= 6; + id_type = acl_entry_link->entryp->ace_id->id_type; + DEBUG(10,("The id_type is %d\n",id_type)); + + switch(id_type) { + case SMB_ACL_USER_OBJ: + file_acl->u_access = acl_entry_link->entryp->ace_access; + continue; + case SMB_ACL_GROUP_OBJ: + file_acl->g_access = acl_entry_link->entryp->ace_access; + continue; + case SMB_ACL_OTHER: + file_acl->o_access = acl_entry_link->entryp->ace_access; + continue; + case SMB_ACL_MASK: + continue; + } + + if((file_acl->acl_len + sizeof(struct acl_entry)) > acl_length) { + acl_length += sizeof(struct acl_entry); + file_acl_temp = (struct acl *)SMB_MALLOC(acl_length); + if(file_acl_temp == NULL) { + SAFE_FREE(file_acl); + errno = ENOMEM; + DEBUG(0,("Error in sys_acl_set_fd is %d\n",errno)); + return(-1); + } + + memcpy(file_acl_temp,file_acl,file_acl->acl_len); + SAFE_FREE(file_acl); + file_acl = file_acl_temp; + } + + acl_entry = (struct acl_entry *)((char *)file_acl + file_acl->acl_len); + file_acl->acl_len += sizeof(struct acl_entry); + acl_entry->ace_len = acl_entry_link->entryp->ace_len; + acl_entry->ace_access = acl_entry_link->entryp->ace_access; + + /* In order to use this, we'll need to wait until we can get denies */ + /* if(!acl_entry->ace_access && acl_entry->ace_type == ACC_PERMIT) + acl_entry->ace_type = ACC_SPECIFY; */ + + acl_entry->ace_type = ACC_SPECIFY; + + ace_id = acl_entry->ace_id; + + ace_id->id_type = acl_entry_link->entryp->ace_id->id_type; + DEBUG(10,("The id type is %d\n",ace_id->id_type)); + ace_id->id_len = acl_entry_link->entryp->ace_id->id_len; + memcpy(&user_id, acl_entry_link->entryp->ace_id->id_data, sizeof(uid_t)); + memcpy(ace_id->id_data, &user_id, sizeof(uid_t)); + } + + rc = fchacl(fd,file_acl,file_acl->acl_len); + DEBUG(10,("errno is %d\n",errno)); + DEBUG(10,("return code is %d\n",rc)); + SAFE_FREE(file_acl); + DEBUG(10,("Exiting sys_acl_set_fd\n")); + return(rc); +} +#endif + +int sys_acl_delete_def_file(UNUSED(const char *name)) +{ + /* AIX has no default ACL */ + return 0; +} + +int sys_acl_free_acl(SMB_ACL_T posix_acl) +{ + struct acl_entry_link *acl_entry_link; + + for(acl_entry_link = posix_acl->nextp; acl_entry_link->nextp != NULL; acl_entry_link = acl_entry_link->nextp) { + SAFE_FREE(acl_entry_link->prevp->entryp); + SAFE_FREE(acl_entry_link->prevp); + } + + SAFE_FREE(acl_entry_link->prevp->entryp); + SAFE_FREE(acl_entry_link->prevp); + SAFE_FREE(acl_entry_link->entryp); + SAFE_FREE(acl_entry_link); + + return(0); +} + +#elif defined(HAVE_OSX_ACLS) /*----------------------------------------------*/ + +#define OSX_BROKEN_GETENTRY /* returns 0 instead of 1 */ + +#include + +int sys_acl_get_entry(SMB_ACL_T the_acl, int entry_id, SMB_ACL_ENTRY_T *entry_p) +{ + int ret = acl_get_entry(the_acl, entry_id, entry_p); +#ifdef OSX_BROKEN_GETENTRY + if (ret == 0) + ret = 1; + else if (ret == -1 && errno == 22) + ret = 0; +#endif + return ret; +} + +SMB_ACL_T sys_acl_get_file(const char *path_p, SMB_ACL_TYPE_T type) +{ + if (type == ACL_TYPE_DEFAULT) { + errno = ENOTSUP; + return NULL; + } + errno = 0; + return acl_get_file(path_p, type); +} + +#if 0 +SMB_ACL_T sys_acl_get_fd(int fd) +{ + return acl_get_fd(fd); +} +#endif + +int sys_acl_get_info(SMB_ACL_ENTRY_T entry, SMB_ACL_TAG_T *tag_type_p, uint32 *bits_p, id_t *u_g_id_p) +{ + uuid_t *uup; + acl_tag_t tag; + acl_flagset_t flagset; + acl_permset_t permset; + uint32 bits, fb, bb, pb; + int id_type = -1; + int rc; + + if (acl_get_tag_type(entry, &tag) != 0 + || acl_get_flagset_np(entry, &flagset) != 0 + || acl_get_permset(entry, &permset) != 0 + || (uup = acl_get_qualifier(entry)) == NULL) + return -1; + + rc = mbr_uuid_to_id(*uup, u_g_id_p, &id_type); + acl_free(uup); + if (rc != 0) + return rc; + + if (id_type == ID_TYPE_UID) + *tag_type_p = SMB_ACL_USER; + else + *tag_type_p = SMB_ACL_GROUP; + + bits = tag == ACL_EXTENDED_ALLOW ? 1 : 0; + + for (fb = (1u<<4), bb = (1u<<1); bb < (1u<<12); fb *= 2, bb *= 2) { + if (acl_get_flag_np(flagset, fb) == 1) + bits |= bb; + } + + for (pb = (1u<<1), bb = (1u<<12); bb < (1u<<25); pb *= 2, bb *= 2) { + if (acl_get_perm_np(permset, pb) == 1) + bits |= bb; + } + + *bits_p = bits; + + return 0; +} + +SMB_ACL_T sys_acl_init(int count) +{ + return acl_init(count); +} + +int sys_acl_create_entry(SMB_ACL_T *pacl, SMB_ACL_ENTRY_T *pentry) +{ + return acl_create_entry(pacl, pentry); +} + +int sys_acl_set_info(SMB_ACL_ENTRY_T entry, SMB_ACL_TAG_T tag_type, uint32 bits, id_t u_g_id) +{ + acl_flagset_t flagset; + acl_permset_t permset; + uint32 fb, bb, pb; + int is_user = tag_type == SMB_ACL_USER; + uuid_t uu; + int rc; + + tag_type = bits & 1 ? ACL_EXTENDED_ALLOW : ACL_EXTENDED_DENY; + + if (acl_get_flagset_np(entry, &flagset) != 0 + || acl_get_permset(entry, &permset) != 0) + return -1; + + acl_clear_flags_np(flagset); + acl_clear_perms(permset); + + for (fb = (1u<<4), bb = (1u<<1); bb < (1u<<12); fb *= 2, bb *= 2) { + if (bits & bb) + acl_add_flag_np(flagset, fb); + } + + for (pb = (1u<<1), bb = (1u<<12); bb < (1u<<25); pb *= 2, bb *= 2) { + if (bits & bb) + acl_add_perm(permset, pb); + } + + if (is_user) + rc = mbr_uid_to_uuid(u_g_id, uu); + else + rc = mbr_gid_to_uuid(u_g_id, uu); + if (rc != 0) + return rc; + + if (acl_set_tag_type(entry, tag_type) != 0 + || acl_set_qualifier(entry, &uu) != 0 + || acl_set_permset(entry, permset) != 0 + || acl_set_flagset_np(entry, flagset) != 0) + return -1; + + return 0; +} + +#if 0 +int sys_acl_set_access_bits(SMB_ACL_ENTRY_T entry, uint32 bits) +{ + return -1; /* Not needed for OS X. */ +} +#endif + +int sys_acl_valid(SMB_ACL_T theacl) +{ + return acl_valid(theacl); +} + +int sys_acl_set_file(const char *name, SMB_ACL_TYPE_T acltype, SMB_ACL_T theacl) +{ + return acl_set_file(name, acltype, theacl); +} + +#if 0 +int sys_acl_set_fd(int fd, SMB_ACL_T theacl) +{ + return acl_set_fd(fd, theacl); +} +#endif + +int sys_acl_delete_def_file(const char *name) +{ + return acl_delete_def_file(name); +} + +int sys_acl_free_acl(SMB_ACL_T the_acl) +{ + return acl_free(the_acl); +} + +#else /* No ACLs. */ + +#error No ACL functions defined for this platform! + +#endif + +/************************************************************************ + Deliberately outside the ACL defines. Return 1 if this is a "no acls" + errno, 0 if not. +************************************************************************/ + +int no_acl_syscall_error(int err) +{ +#ifdef HAVE_OSX_ACLS + if (err == ENOENT) + return 1; /* Weird problem with directory ACLs. */ +#endif +#if defined(ENOSYS) + if (err == ENOSYS) { + return 1; + } +#endif +#if defined(ENOTSUP) + if (err == ENOTSUP) { + return 1; + } +#endif + if (err == EINVAL) { + /* If the type of SMB_ACL_TYPE_ACCESS or SMB_ACL_TYPE_DEFAULT + * isn't valid, then the ACLs must be non-POSIX. */ + return 1; + } + return 0; +} + +#endif /* SUPPORT_ACLS */ diff --git a/rsync/lib/sysacls.h b/rsync/lib/sysacls.h new file mode 100644 index 0000000..31c4909 --- /dev/null +++ b/rsync/lib/sysacls.h @@ -0,0 +1,305 @@ +/* + * Unix SMB/Netbios implementation. + * Version 2.2.x + * Portable SMB ACL interface + * Copyright (C) Jeremy Allison 2000 + * Copyright (C) 2007-2014 Wayne Davison + * + * 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 + * with this program; if not, visit the http://fsf.org website. + */ + +#ifdef SUPPORT_ACLS + +#ifdef HAVE_SYS_ACL_H +#include +#endif +#ifdef HAVE_ACL_LIBACL_H +#include +#endif + +#define SMB_MALLOC(cnt) new_array(char, cnt) +#define SMB_MALLOC_P(obj) new_array(obj, 1) +#define SMB_MALLOC_ARRAY(obj, cnt) new_array(obj, cnt) +#define SMB_REALLOC(mem, cnt) realloc_array(mem, char, cnt) +#define slprintf snprintf + +#if defined HAVE_POSIX_ACLS /*-----------------------------------------------*/ + +/* This is an identity mapping (just remove the SMB_). */ + +#define SMB_ACL_TAG_T acl_tag_t +#define SMB_ACL_TYPE_T acl_type_t + +/* Types of ACLs. */ +#define SMB_ACL_USER ACL_USER +#define SMB_ACL_USER_OBJ ACL_USER_OBJ +#define SMB_ACL_GROUP ACL_GROUP +#define SMB_ACL_GROUP_OBJ ACL_GROUP_OBJ +#define SMB_ACL_OTHER ACL_OTHER +#define SMB_ACL_MASK ACL_MASK + +#define SMB_ACL_T acl_t + +#define SMB_ACL_ENTRY_T acl_entry_t + +#define SMB_ACL_FIRST_ENTRY ACL_FIRST_ENTRY +#define SMB_ACL_NEXT_ENTRY ACL_NEXT_ENTRY + +#define SMB_ACL_TYPE_ACCESS ACL_TYPE_ACCESS +#define SMB_ACL_TYPE_DEFAULT ACL_TYPE_DEFAULT + +#define SMB_ACL_VALID_NAME_BITS (4 | 2 | 1) +#define SMB_ACL_VALID_OBJ_BITS (4 | 2 | 1) + +#define SMB_ACL_NEED_SORT + +#elif defined HAVE_TRU64_ACLS /*---------------------------------------------*/ + +/* This is for DEC/Compaq Tru64 UNIX */ + +#define SMB_ACL_TAG_T acl_tag_t +#define SMB_ACL_TYPE_T acl_type_t + +/* Types of ACLs. */ +#define SMB_ACL_USER ACL_USER +#define SMB_ACL_USER_OBJ ACL_USER_OBJ +#define SMB_ACL_GROUP ACL_GROUP +#define SMB_ACL_GROUP_OBJ ACL_GROUP_OBJ +#define SMB_ACL_OTHER ACL_OTHER +#define SMB_ACL_MASK ACL_MASK + +#define SMB_ACL_T acl_t + +#define SMB_ACL_ENTRY_T acl_entry_t + +#define SMB_ACL_FIRST_ENTRY 0 +#define SMB_ACL_NEXT_ENTRY 1 + +#define SMB_ACL_TYPE_ACCESS ACL_TYPE_ACCESS +#define SMB_ACL_TYPE_DEFAULT ACL_TYPE_DEFAULT + +#define SMB_ACL_VALID_NAME_BITS (4 | 2 | 1) +#define SMB_ACL_VALID_OBJ_BITS (4 | 2 | 1) + +#define SMB_ACL_NEED_SORT + +#elif defined HAVE_UNIXWARE_ACLS || defined HAVE_SOLARIS_ACLS /*-------------*/ + +/* Donated by Michael Davidson for UnixWare / OpenUNIX. + * Modified by Toomas Soome for Solaris. */ + +/* SVR4.2 ES/MP ACLs */ +typedef int SMB_ACL_TAG_T; +typedef int SMB_ACL_TYPE_T; + +/* Types of ACLs. */ +#define SMB_ACL_USER USER +#define SMB_ACL_USER_OBJ USER_OBJ +#define SMB_ACL_GROUP GROUP +#define SMB_ACL_GROUP_OBJ GROUP_OBJ +#define SMB_ACL_OTHER OTHER_OBJ +#define SMB_ACL_MASK CLASS_OBJ + +typedef struct SMB_ACL_T { + int size; + int count; + int next; + struct acl acl[1]; +} *SMB_ACL_T; + +typedef struct acl *SMB_ACL_ENTRY_T; + +#define SMB_ACL_FIRST_ENTRY 0 +#define SMB_ACL_NEXT_ENTRY 1 + +#define SMB_ACL_TYPE_ACCESS 0 +#define SMB_ACL_TYPE_DEFAULT 1 + +#define SMB_ACL_VALID_NAME_BITS (4 | 2 | 1) +#define SMB_ACL_VALID_OBJ_BITS (4 | 2 | 1) + +#define SMB_ACL_NEED_SORT + +#ifdef __CYGWIN__ +#define SMB_ACL_LOSES_SPECIAL_MODE_BITS +#endif + +#elif defined HAVE_HPUX_ACLS /*----------------------------------------------*/ + +/* Based on the Solaris & UnixWare code. */ + +#undef GROUP +#include + +/* SVR4.2 ES/MP ACLs */ +typedef int SMB_ACL_TAG_T; +typedef int SMB_ACL_TYPE_T; + +/* Types of ACLs. */ +#define SMB_ACL_USER USER +#define SMB_ACL_USER_OBJ USER_OBJ +#define SMB_ACL_GROUP GROUP +#define SMB_ACL_GROUP_OBJ GROUP_OBJ +#define SMB_ACL_OTHER OTHER_OBJ +#define SMB_ACL_MASK CLASS_OBJ + +typedef struct SMB_ACL_T { + int size; + int count; + int next; + struct acl acl[1]; +} *SMB_ACL_T; + +typedef struct acl *SMB_ACL_ENTRY_T; + +#define SMB_ACL_FIRST_ENTRY 0 +#define SMB_ACL_NEXT_ENTRY 1 + +#define SMB_ACL_TYPE_ACCESS 0 +#define SMB_ACL_TYPE_DEFAULT 1 + +#define SMB_ACL_VALID_NAME_BITS (4 | 2 | 1) +#define SMB_ACL_VALID_OBJ_BITS (4 | 2 | 1) + +#define SMB_ACL_NEED_SORT + +#elif defined HAVE_IRIX_ACLS /*----------------------------------------------*/ + +/* IRIX ACLs */ + +#define SMB_ACL_TAG_T acl_tag_t +#define SMB_ACL_TYPE_T acl_type_t + +/* Types of ACLs. */ +#define SMB_ACL_USER ACL_USER +#define SMB_ACL_USER_OBJ ACL_USER_OBJ +#define SMB_ACL_GROUP ACL_GROUP +#define SMB_ACL_GROUP_OBJ ACL_GROUP_OBJ +#define SMB_ACL_OTHER ACL_OTHER_OBJ +#define SMB_ACL_MASK ACL_MASK + +typedef struct SMB_ACL_T { + int next; + BOOL freeaclp; + struct acl *aclp; +} *SMB_ACL_T; + +#define SMB_ACL_ENTRY_T acl_entry_t + +#define SMB_ACL_FIRST_ENTRY 0 +#define SMB_ACL_NEXT_ENTRY 1 + +#define SMB_ACL_TYPE_ACCESS ACL_TYPE_ACCESS +#define SMB_ACL_TYPE_DEFAULT ACL_TYPE_DEFAULT + +#define SMB_ACL_VALID_NAME_BITS (4 | 2 | 1) +#define SMB_ACL_VALID_OBJ_BITS (4 | 2 | 1) + +#define SMB_ACL_NEED_SORT + +#elif defined HAVE_AIX_ACLS /*-----------------------------------------------*/ + +/* Donated by Medha Date, mdate@austin.ibm.com, for IBM */ + +#include "/usr/include/acl.h" + +struct acl_entry_link{ + struct acl_entry_link *prevp; + struct new_acl_entry *entryp; + struct acl_entry_link *nextp; + int count; +}; + +struct new_acl_entry{ + unsigned short ace_len; + unsigned short ace_type; + unsigned int ace_access; + struct ace_id ace_id[1]; +}; + +#define SMB_ACL_ENTRY_T struct new_acl_entry* +#define SMB_ACL_T struct acl_entry_link* + +#define SMB_ACL_TAG_T unsigned short +#define SMB_ACL_TYPE_T int + +/* Types of ACLs. */ +#define SMB_ACL_USER ACEID_USER +#define SMB_ACL_USER_OBJ 3 +#define SMB_ACL_GROUP ACEID_GROUP +#define SMB_ACL_GROUP_OBJ 4 +#define SMB_ACL_OTHER 5 +#define SMB_ACL_MASK 6 + +#define SMB_ACL_FIRST_ENTRY 1 +#define SMB_ACL_NEXT_ENTRY 2 + +#define SMB_ACL_TYPE_ACCESS 0 +#define SMB_ACL_TYPE_DEFAULT 1 + +#define SMB_ACL_VALID_NAME_BITS (4 | 2 | 1) +#define SMB_ACL_VALID_OBJ_BITS (4 | 2 | 1) + +#define SMB_ACL_NEED_SORT + +#elif defined(HAVE_OSX_ACLS) /*----------------------------------------------*/ + +/* Special handling for OS X ACLs */ + +#define SMB_ACL_TAG_T acl_tag_t +#define SMB_ACL_TYPE_T acl_type_t + +#define SMB_ACL_T acl_t + +#define SMB_ACL_ENTRY_T acl_entry_t + +#define SMB_ACL_USER 1 +#define SMB_ACL_GROUP 2 + +#define SMB_ACL_FIRST_ENTRY ACL_FIRST_ENTRY +#define SMB_ACL_NEXT_ENTRY ACL_NEXT_ENTRY + +#define SMB_ACL_TYPE_ACCESS ACL_TYPE_EXTENDED +#define SMB_ACL_TYPE_DEFAULT ACL_TYPE_DEFAULT + +#define SMB_ACL_VALID_NAME_BITS ((1<<25)-1) +#define SMB_ACL_VALID_OBJ_BITS 0 + +/*#undef SMB_ACL_NEED_SORT*/ + +#else /*---------------------------------------------------------------------*/ + +/* Unknown platform. */ + +#error Cannot handle ACLs on this platform! + +#endif + +int sys_acl_get_entry(SMB_ACL_T the_acl, int entry_id, SMB_ACL_ENTRY_T *entry_p); +int sys_acl_get_tag_type(SMB_ACL_ENTRY_T entry_d, SMB_ACL_TAG_T *tag_type_p); +int sys_acl_get_info(SMB_ACL_ENTRY_T entry, SMB_ACL_TAG_T *tag_type_p, uint32 *bits_p, id_t *u_g_id_p); +SMB_ACL_T sys_acl_get_file(const char *path_p, SMB_ACL_TYPE_T type); +SMB_ACL_T sys_acl_get_fd(int fd); +SMB_ACL_T sys_acl_init(int count); +int sys_acl_create_entry(SMB_ACL_T *pacl, SMB_ACL_ENTRY_T *pentry); +int sys_acl_set_info(SMB_ACL_ENTRY_T entry, SMB_ACL_TAG_T tagtype, uint32 bits, id_t u_g_id); +int sys_acl_set_access_bits(SMB_ACL_ENTRY_T entry, uint32 bits); +int sys_acl_valid(SMB_ACL_T theacl); +int sys_acl_set_file(const char *name, SMB_ACL_TYPE_T acltype, SMB_ACL_T theacl); +int sys_acl_set_fd(int fd, SMB_ACL_T theacl); +int sys_acl_delete_def_file(const char *name); +int sys_acl_free_acl(SMB_ACL_T the_acl); +int no_acl_syscall_error(int err); + +#endif /* SUPPORT_ACLS */ diff --git a/rsync/lib/sysxattrs.c b/rsync/lib/sysxattrs.c new file mode 100644 index 0000000..f02802a --- /dev/null +++ b/rsync/lib/sysxattrs.c @@ -0,0 +1,300 @@ +/* + * Extended attribute support for rsync. + * + * Copyright (C) 2004 Red Hat, Inc. + * Copyright (C) 2003-2014 Wayne Davison + * Written by Jay Fenlason. + * + * 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, visit the http://fsf.org website. + */ + +#include "rsync.h" +#include "sysxattrs.h" + +#ifdef SUPPORT_XATTRS + +#ifdef HAVE_OSX_XATTRS +#define GETXATTR_FETCH_LIMIT (64*1024*1024) +#endif + +#if defined HAVE_LINUX_XATTRS + +ssize_t sys_lgetxattr(const char *path, const char *name, void *value, size_t size) +{ + return lgetxattr(path, name, value, size); +} + +ssize_t sys_fgetxattr(int filedes, const char *name, void *value, size_t size) +{ + return fgetxattr(filedes, name, value, size); +} + +int sys_lsetxattr(const char *path, const char *name, const void *value, size_t size) +{ + return lsetxattr(path, name, value, size, 0); +} + +int sys_lremovexattr(const char *path, const char *name) +{ + return lremovexattr(path, name); +} + +ssize_t sys_llistxattr(const char *path, char *list, size_t size) +{ + return llistxattr(path, list, size); +} + +#elif HAVE_OSX_XATTRS + +ssize_t sys_lgetxattr(const char *path, const char *name, void *value, size_t size) +{ + ssize_t len = getxattr(path, name, value, size, 0, XATTR_NOFOLLOW); + + /* If we're retrieving data, handle resource forks > 64MB specially */ + if (value != NULL && len == GETXATTR_FETCH_LIMIT && (size_t)len < size) { + /* getxattr will only return 64MB of data at a time, need to call again with a new offset */ + u_int32_t offset = len; + size_t data_retrieved = len; + while (data_retrieved < size) { + len = getxattr(path, name, value + offset, size - data_retrieved, offset, XATTR_NOFOLLOW); + if (len <= 0) + break; + data_retrieved += len; + offset += (u_int32_t)len; + } + len = data_retrieved; + } + + return len; +} + +ssize_t sys_fgetxattr(int filedes, const char *name, void *value, size_t size) +{ + return fgetxattr(filedes, name, value, size, 0, 0); +} + +int sys_lsetxattr(const char *path, const char *name, const void *value, size_t size) +{ + return setxattr(path, name, value, size, 0, XATTR_NOFOLLOW); +} + +int sys_lremovexattr(const char *path, const char *name) +{ + return removexattr(path, name, XATTR_NOFOLLOW); +} + +ssize_t sys_llistxattr(const char *path, char *list, size_t size) +{ + return listxattr(path, list, size, XATTR_NOFOLLOW); +} + +#elif HAVE_FREEBSD_XATTRS + +ssize_t sys_lgetxattr(const char *path, const char *name, void *value, size_t size) +{ + return extattr_get_link(path, EXTATTR_NAMESPACE_USER, name, value, size); +} + +ssize_t sys_fgetxattr(int filedes, const char *name, void *value, size_t size) +{ + return extattr_get_fd(filedes, EXTATTR_NAMESPACE_USER, name, value, size); +} + +int sys_lsetxattr(const char *path, const char *name, const void *value, size_t size) +{ + return extattr_set_link(path, EXTATTR_NAMESPACE_USER, name, value, size); +} + +int sys_lremovexattr(const char *path, const char *name) +{ + return extattr_delete_link(path, EXTATTR_NAMESPACE_USER, name); +} + +ssize_t sys_llistxattr(const char *path, char *list, size_t size) +{ + unsigned char keylen; + ssize_t off, len = extattr_list_link(path, EXTATTR_NAMESPACE_USER, list, size); + + if (len <= 0 || (size_t)len > size) + return len; + + /* FreeBSD puts a single-byte length before each string, with no '\0' + * terminator. We need to change this into a series of null-terminted + * strings. Since the size is the same, we can simply transform the + * output in place. */ + for (off = 0; off < len; off += keylen + 1) { + keylen = ((unsigned char*)list)[off]; + if (off + keylen >= len) { + /* Should be impossible, but kernel bugs happen! */ + errno = EINVAL; + return -1; + } + memmove(list+off, list+off+1, keylen); + list[off+keylen] = '\0'; + } + + return len; +} + +#elif HAVE_SOLARIS_XATTRS + +static ssize_t read_xattr(int attrfd, void *buf, size_t buflen) +{ + STRUCT_STAT sb; + ssize_t ret; + + if (fstat(attrfd, &sb) < 0) + ret = -1; + else if (sb.st_size > SSIZE_MAX) { + errno = ERANGE; + ret = -1; + } else if (buflen == 0) + ret = sb.st_size; + else if (sb.st_size > buflen) { + errno = ERANGE; + ret = -1; + } else { + size_t bufpos; + for (bufpos = 0; bufpos < sb.st_size; ) { + ssize_t cnt = read(attrfd, buf + bufpos, sb.st_size - bufpos); + if (cnt <= 0) { + if (cnt < 0 && errno == EINTR) + continue; + bufpos = -1; + break; + } + bufpos += cnt; + } + ret = bufpos; + } + + close(attrfd); + + return ret; +} + +ssize_t sys_lgetxattr(const char *path, const char *name, void *value, size_t size) +{ + int attrfd; + + if ((attrfd = attropen(path, name, O_RDONLY)) < 0) { + errno = ENOATTR; + return -1; + } + + return read_xattr(attrfd, value, size); +} + +ssize_t sys_fgetxattr(int filedes, const char *name, void *value, size_t size) +{ + int attrfd; + + if ((attrfd = openat(filedes, name, O_RDONLY|O_XATTR, 0)) < 0) { + errno = ENOATTR; + return -1; + } + + return read_xattr(attrfd, value, size); +} + +int sys_lsetxattr(const char *path, const char *name, const void *value, size_t size) +{ + int attrfd; + size_t bufpos; + mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP; + + if ((attrfd = attropen(path, name, O_CREAT|O_TRUNC|O_WRONLY, mode)) < 0) + return -1; + + for (bufpos = 0; bufpos < size; ) { + ssize_t cnt = write(attrfd, value+bufpos, size); + if (cnt <= 0) { + if (cnt < 0 && errno == EINTR) + continue; + bufpos = -1; + break; + } + bufpos += cnt; + } + + close(attrfd); + + return bufpos > 0 ? 0 : -1; +} + +int sys_lremovexattr(const char *path, const char *name) +{ + int attrdirfd; + int ret; + + if ((attrdirfd = attropen(path, ".", O_RDONLY)) < 0) + return -1; + + ret = unlinkat(attrdirfd, name, 0); + + close(attrdirfd); + + return ret; +} + +ssize_t sys_llistxattr(const char *path, char *list, size_t size) +{ + int attrdirfd; + DIR *dirp; + struct dirent *dp; + ssize_t ret = 0; + + if ((attrdirfd = attropen(path, ".", O_RDONLY)) < 0) { + errno = ENOTSUP; + return -1; + } + + if ((dirp = fdopendir(attrdirfd)) == NULL) { + close(attrdirfd); + return -1; + } + + while ((dp = readdir(dirp))) { + int len = strlen(dp->d_name); + + if (dp->d_name[0] == '.' && (len == 1 || (len == 2 && dp->d_name[1] == '.'))) + continue; + if (len == 11 && dp->d_name[0] == 'S' && strncmp(dp->d_name, "SUNWattr_r", 10) == 0 + && (dp->d_name[10] == 'o' || dp->d_name[10] == 'w')) + continue; + + if ((ret += len+1) > size) { + if (size == 0) + continue; + ret = -1; + errno = ERANGE; + break; + } + memcpy(list, dp->d_name, len+1); + list += len+1; + } + + closedir(dirp); + close(attrdirfd); + + return ret; +} + +#else + +#error You need to create xattr compatibility functions. + +#endif + +#endif /* SUPPORT_XATTRS */ diff --git a/rsync/lib/sysxattrs.h b/rsync/lib/sysxattrs.h new file mode 100644 index 0000000..428421a --- /dev/null +++ b/rsync/lib/sysxattrs.h @@ -0,0 +1,26 @@ +#ifdef SUPPORT_XATTRS + +#if defined HAVE_ATTR_XATTR_H +#include +#elif defined HAVE_SYS_XATTR_H +#include +#elif defined HAVE_SYS_EXTATTR_H +#include +#endif + +/* Linux 2.4 does not define this as a distinct errno value: */ +#ifndef ENOATTR +#define ENOATTR ENODATA +#endif + +ssize_t sys_lgetxattr(const char *path, const char *name, void *value, size_t size); +ssize_t sys_fgetxattr(int filedes, const char *name, void *value, size_t size); +int sys_lsetxattr(const char *path, const char *name, const void *value, size_t size); +int sys_lremovexattr(const char *path, const char *name); +ssize_t sys_llistxattr(const char *path, char *list, size_t size); + +#else + +/* No xattrs available */ + +#endif diff --git a/rsync/lib/wildmatch.c b/rsync/lib/wildmatch.c new file mode 100644 index 0000000..f3a1731 --- /dev/null +++ b/rsync/lib/wildmatch.c @@ -0,0 +1,368 @@ +/* +** Do shell-style pattern matching for ?, \, [], and * characters. +** It is 8bit clean. +** +** Written by Rich $alz, mirror!rs, Wed Nov 26 19:03:17 EST 1986. +** Rich $alz is now . +** +** Modified by Wayne Davison to special-case '/' matching, to make '**' +** work differently than '*', and to fix the character-class code. +*/ + +#include "rsync.h" + +/* What character marks an inverted character class? */ +#define NEGATE_CLASS '!' +#define NEGATE_CLASS2 '^' + +#define FALSE 0 +#define TRUE 1 +#define ABORT_ALL -1 +#define ABORT_TO_STARSTAR -2 + +#define CC_EQ(class, len, litmatch) ((len) == sizeof (litmatch)-1 \ + && *(class) == *(litmatch) \ + && strncmp((char*)class, litmatch, len) == 0) + +#if defined STDC_HEADERS || !defined isascii +# define ISASCII(c) 1 +#else +# define ISASCII(c) isascii(c) +#endif + +#ifdef isblank +# define ISBLANK(c) (ISASCII(c) && isblank(c)) +#else +# define ISBLANK(c) ((c) == ' ' || (c) == '\t') +#endif + +#ifdef isgraph +# define ISGRAPH(c) (ISASCII(c) && isgraph(c)) +#else +# define ISGRAPH(c) (ISASCII(c) && isprint(c) && !isspace(c)) +#endif + +#define ISPRINT(c) (ISASCII(c) && isprint(c)) +#define ISDIGIT(c) (ISASCII(c) && isdigit(c)) +#define ISALNUM(c) (ISASCII(c) && isalnum(c)) +#define ISALPHA(c) (ISASCII(c) && isalpha(c)) +#define ISCNTRL(c) (ISASCII(c) && iscntrl(c)) +#define ISLOWER(c) (ISASCII(c) && islower(c)) +#define ISPUNCT(c) (ISASCII(c) && ispunct(c)) +#define ISSPACE(c) (ISASCII(c) && isspace(c)) +#define ISUPPER(c) (ISASCII(c) && isupper(c)) +#define ISXDIGIT(c) (ISASCII(c) && isxdigit(c)) + +#ifdef WILD_TEST_ITERATIONS +int wildmatch_iteration_count; +#endif + +static int force_lower_case = 0; + +/* Match pattern "p" against the a virtually-joined string consisting + * of "text" and any strings in array "a". */ +static int dowild(const uchar *p, const uchar *text, const uchar*const *a) +{ + uchar p_ch; + +#ifdef WILD_TEST_ITERATIONS + wildmatch_iteration_count++; +#endif + + for ( ; (p_ch = *p) != '\0'; text++, p++) { + int matched, special; + uchar t_ch, prev_ch; + while ((t_ch = *text) == '\0') { + if (*a == NULL) { + if (p_ch != '*') + return ABORT_ALL; + break; + } + text = *a++; + } + if (force_lower_case && ISUPPER(t_ch)) + t_ch = tolower(t_ch); + switch (p_ch) { + case '\\': + /* Literal match with following character. Note that the test + * in "default" handles the p[1] == '\0' failure case. */ + p_ch = *++p; + /* FALLTHROUGH */ + default: + if (t_ch != p_ch) + return FALSE; + continue; + case '?': + /* Match anything but '/'. */ + if (t_ch == '/') + return FALSE; + continue; + case '*': + if (*++p == '*') { + while (*++p == '*') {} + special = TRUE; + } else + special = FALSE; + if (*p == '\0') { + /* Trailing "**" matches everything. Trailing "*" matches + * only if there are no more slash characters. */ + if (!special) { + do { + if (strchr((char*)text, '/') != NULL) + return FALSE; + } while ((text = *a++) != NULL); + } + return TRUE; + } + while (1) { + if (t_ch == '\0') { + if ((text = *a++) == NULL) + break; + t_ch = *text; + continue; + } + if ((matched = dowild(p, text, a)) != FALSE) { + if (!special || matched != ABORT_TO_STARSTAR) + return matched; + } else if (!special && t_ch == '/') + return ABORT_TO_STARSTAR; + t_ch = *++text; + } + return ABORT_ALL; + case '[': + p_ch = *++p; +#ifdef NEGATE_CLASS2 + if (p_ch == NEGATE_CLASS2) + p_ch = NEGATE_CLASS; +#endif + /* Assign literal TRUE/FALSE because of "matched" comparison. */ + special = p_ch == NEGATE_CLASS? TRUE : FALSE; + if (special) { + /* Inverted character class. */ + p_ch = *++p; + } + prev_ch = 0; + matched = FALSE; + do { + if (!p_ch) + return ABORT_ALL; + if (p_ch == '\\') { + p_ch = *++p; + if (!p_ch) + return ABORT_ALL; + if (t_ch == p_ch) + matched = TRUE; + } else if (p_ch == '-' && prev_ch && p[1] && p[1] != ']') { + p_ch = *++p; + if (p_ch == '\\') { + p_ch = *++p; + if (!p_ch) + return ABORT_ALL; + } + if (t_ch <= p_ch && t_ch >= prev_ch) + matched = TRUE; + p_ch = 0; /* This makes "prev_ch" get set to 0. */ + } else if (p_ch == '[' && p[1] == ':') { + const uchar *s; + int i; + for (s = p += 2; (p_ch = *p) && p_ch != ']'; p++) {} /*SHARED ITERATOR*/ + if (!p_ch) + return ABORT_ALL; + i = p - s - 1; + if (i < 0 || p[-1] != ':') { + /* Didn't find ":]", so treat like a normal set. */ + p = s - 2; + p_ch = '['; + if (t_ch == p_ch) + matched = TRUE; + continue; + } + if (CC_EQ(s,i, "alnum")) { + if (ISALNUM(t_ch)) + matched = TRUE; + } else if (CC_EQ(s,i, "alpha")) { + if (ISALPHA(t_ch)) + matched = TRUE; + } else if (CC_EQ(s,i, "blank")) { + if (ISBLANK(t_ch)) + matched = TRUE; + } else if (CC_EQ(s,i, "cntrl")) { + if (ISCNTRL(t_ch)) + matched = TRUE; + } else if (CC_EQ(s,i, "digit")) { + if (ISDIGIT(t_ch)) + matched = TRUE; + } else if (CC_EQ(s,i, "graph")) { + if (ISGRAPH(t_ch)) + matched = TRUE; + } else if (CC_EQ(s,i, "lower")) { + if (ISLOWER(t_ch)) + matched = TRUE; + } else if (CC_EQ(s,i, "print")) { + if (ISPRINT(t_ch)) + matched = TRUE; + } else if (CC_EQ(s,i, "punct")) { + if (ISPUNCT(t_ch)) + matched = TRUE; + } else if (CC_EQ(s,i, "space")) { + if (ISSPACE(t_ch)) + matched = TRUE; + } else if (CC_EQ(s,i, "upper")) { + if (ISUPPER(t_ch)) + matched = TRUE; + } else if (CC_EQ(s,i, "xdigit")) { + if (ISXDIGIT(t_ch)) + matched = TRUE; + } else /* malformed [:class:] string */ + return ABORT_ALL; + p_ch = 0; /* This makes "prev_ch" get set to 0. */ + } else if (t_ch == p_ch) + matched = TRUE; + } while (prev_ch = p_ch, (p_ch = *++p) != ']'); + if (matched == special || t_ch == '/') + return FALSE; + continue; + } + } + + do { + if (*text) + return FALSE; + } while ((text = *a++) != NULL); + + return TRUE; +} + +/* Match literal string "s" against the a virtually-joined string consisting + * of "text" and any strings in array "a". */ +static int doliteral(const uchar *s, const uchar *text, const uchar*const *a) +{ + for ( ; *s != '\0'; text++, s++) { + while (*text == '\0') { + if ((text = *a++) == NULL) + return FALSE; + } + if (*text != *s) + return FALSE; + } + + do { + if (*text) + return FALSE; + } while ((text = *a++) != NULL); + + return TRUE; +} + +/* Return the last "count" path elements from the concatenated string. + * We return a string pointer to the start of the string, and update the + * array pointer-pointer to point to any remaining string elements. */ +static const uchar *trailing_N_elements(const uchar*const **a_ptr, int count) +{ + const uchar*const *a = *a_ptr; + const uchar*const *first_a = a; + + while (*a) + a++; + + while (a != first_a) { + const uchar *s = *--a; + s += strlen((char*)s); + while (--s >= *a) { + if (*s == '/' && !--count) { + *a_ptr = a+1; + return s+1; + } + } + } + + if (count == 1) { + *a_ptr = a+1; + return *a; + } + + return NULL; +} + +/* Match the "pattern" against the "text" string. */ +int wildmatch(const char *pattern, const char *text) +{ + static const uchar *nomore[1]; /* A NULL pointer. */ +#ifdef WILD_TEST_ITERATIONS + wildmatch_iteration_count = 0; +#endif + return dowild((const uchar*)pattern, (const uchar*)text, nomore) == TRUE; +} + +/* Match the "pattern" against the forced-to-lower-case "text" string. */ +int iwildmatch(const char *pattern, const char *text) +{ + static const uchar *nomore[1]; /* A NULL pointer. */ + int ret; +#ifdef WILD_TEST_ITERATIONS + wildmatch_iteration_count = 0; +#endif + force_lower_case = 1; + ret = dowild((const uchar*)pattern, (const uchar*)text, nomore) == TRUE; + force_lower_case = 0; + return ret; +} + +/* Match pattern "p" against the a virtually-joined string consisting + * of all the pointers in array "texts" (which has a NULL pointer at the + * end). The int "where" can be 0 (normal matching), > 0 (match only + * the trailing N slash-separated filename components of "texts"), or < 0 + * (match the "pattern" at the start or after any slash in "texts"). */ +int wildmatch_array(const char *pattern, const char*const *texts, int where) +{ + const uchar *p = (const uchar*)pattern; + const uchar*const *a = (const uchar*const*)texts; + const uchar *text; + int matched; + +#ifdef WILD_TEST_ITERATIONS + wildmatch_iteration_count = 0; +#endif + + if (where > 0) + text = trailing_N_elements(&a, where); + else + text = *a++; + if (!text) + return FALSE; + + if ((matched = dowild(p, text, a)) != TRUE && where < 0 + && matched != ABORT_ALL) { + while (1) { + if (*text == '\0') { + if ((text = (uchar*)*a++) == NULL) + return FALSE; + continue; + } + if (*text++ == '/' && (matched = dowild(p, text, a)) != FALSE + && matched != ABORT_TO_STARSTAR) + break; + } + } + return matched == TRUE; +} + +/* Match literal string "s" against the a virtually-joined string consisting + * of all the pointers in array "texts" (which has a NULL pointer at the + * end). The int "where" can be 0 (normal matching), or > 0 (match + * only the trailing N slash-separated filename components of "texts"). */ +int litmatch_array(const char *string, const char*const *texts, int where) +{ + const uchar *s = (const uchar*)string; + const uchar*const *a = (const uchar* const*)texts; + const uchar *text; + + if (where > 0) + text = trailing_N_elements(&a, where); + else + text = *a++; + if (!text) + return FALSE; + + return doliteral(s, text, a) == TRUE; +} diff --git a/rsync/lib/wildmatch.h b/rsync/lib/wildmatch.h new file mode 100644 index 0000000..e7f1a35 --- /dev/null +++ b/rsync/lib/wildmatch.h @@ -0,0 +1,6 @@ +/* wildmatch.h */ + +int wildmatch(const char *pattern, const char *text); +int iwildmatch(const char *pattern, const char *text); +int wildmatch_array(const char *pattern, const char*const *texts, int where); +int litmatch_array(const char *string, const char*const *texts, int where); diff --git a/rsync/loadparm.c b/rsync/loadparm.c new file mode 100644 index 0000000..a37bbae --- /dev/null +++ b/rsync/loadparm.c @@ -0,0 +1,846 @@ +/* + * 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, visit the http://fsf.org website. + */ + +/* This is based on loadparm.c from Samba, written by Andrew Tridgell + * and Karl Auer. Some of the changes are: + * + * Copyright (C) 2001, 2002 Martin Pool + * Copyright (C) 2003-2014 Wayne Davison + */ + +/* Load parameters. + * + * This module provides suitable callback functions for the params + * module. It builds the internal table of section details which is + * then used by the rest of the server. + * + * To add a parameter: + * + * 1) add it to the global_vars or local_vars structure definition + * 2) add it to the parm_table + * 3) add it to the list of available functions (eg: using FN_GLOBAL_STRING()) + * 4) initialise it in the Defaults static stucture + * + * Notes: + * The configuration file is processed sequentially for speed. For this + * reason, there is a fair bit of sequence-dependent code here - ie., code + * which assumes that certain things happen before others. In particular, the + * code which happens at the boundary between sections is delicately poised, + * so be careful! + */ + +#include "rsync.h" +#include "itypes.h" + +extern item_list dparam_list; + +#define strequal(a, b) (strcasecmp(a, b)==0) +#define BOOLSTR(b) ((b) ? "Yes" : "No") + +#ifndef LOG_DAEMON +#define LOG_DAEMON 0 +#endif + +#define DEFAULT_DONT_COMPRESS "*.gz *.zip *.z *.rpm *.deb *.iso *.bz2" \ + " *.t[gb]z *.7z *.mp[34] *.mov *.avi *.ogg *.jpg *.jpeg *.png" \ + " *.lzo *.rzip *.lzma *.rar *.ace *.gpg *.xz *.txz *.lz *.tlz" + +/* the following are used by loadparm for option lists */ +typedef enum { + P_BOOL, P_BOOLREV, P_CHAR, P_INTEGER, + P_OCTAL, P_PATH, P_STRING, P_ENUM +} parm_type; + +typedef enum { + P_LOCAL, P_GLOBAL, P_NONE +} parm_class; + +struct enum_list { + int value; + char *name; +}; + +struct parm_struct { + char *label; + parm_type type; + parm_class class; + void *ptr; + struct enum_list *enum_list; + unsigned flags; +}; + +#ifndef GLOBAL_NAME +#define GLOBAL_NAME "global" +#endif + +/* some helpful bits */ +#define iSECTION(i) ((local_vars*)section_list.items)[i] +#define LP_SNUM_OK(i) ((i) >= 0 && (i) < (int)section_list.count) +#define SECTION_PTR(s, p) (((char*)(s)) + (ptrdiff_t)(((char*)(p))-(char*)&Vars.l)) + +/* This structure describes global (ie., server-wide) parameters. */ +typedef struct { + char *bind_address; + char *motd_file; + char *pid_file; + char *socket_options; + + int listen_backlog; + int rsync_port; +} global_vars; + +/* This structure describes a single section. Their order must match the + * initializers below, which you can accomplish by keeping each sub-section + * sorted. (e.g. in vim, just visually select each subsection and use !sort.) + * NOTE: the char* variables MUST all remain at the start of the stuct! */ +typedef struct { + char *auth_users; + char *charset; + char *comment; + char *dont_compress; + char *exclude; + char *exclude_from; + char *filter; + char *gid; + char *hosts_allow; + char *hosts_deny; + char *include; + char *include_from; + char *incoming_chmod; + char *lock_file; + char *log_file; + char *log_format; + char *name; + char *outgoing_chmod; + char *path; + char *postxfer_exec; + char *prexfer_exec; + char *refuse_options; + char *secrets_file; + char *temp_dir; + char *uid; +/* NOTE: update this macro if the last char* variable changes! */ +#define LOCAL_STRING_COUNT() (offsetof(local_vars, uid) / sizeof (char*) + 1) + + int max_connections; + int max_verbosity; + int syslog_facility; + int timeout; + + BOOL fake_super; + BOOL forward_lookup; + BOOL ignore_errors; + BOOL ignore_nonreadable; + BOOL list; + BOOL munge_symlinks; + BOOL numeric_ids; + BOOL read_only; + BOOL reverse_lookup; + BOOL strict_modes; + BOOL transfer_logging; + BOOL use_chroot; + BOOL write_only; +} local_vars; + +/* This structure describes the global variables (g) as well as the globally + * specified values of the local variables (l), which are used when modules + * don't specify their own values. */ +typedef struct { + global_vars g; + local_vars l; +} all_vars; + +/* The application defaults for all the variables. "Defaults" is + * used to re-initialize "Vars" before each config-file read. + * + * In order to keep these sorted in the same way as the structure + * above, use the variable name in the leading comment, including a + * trailing ';' (to avoid a sorting problem with trailing digits). */ +static const all_vars Defaults = { + /* ==== global_vars ==== */ + { + /* bind_address; */ NULL, + /* motd_file; */ NULL, + /* pid_file; */ NULL, + /* socket_options; */ NULL, + + /* listen_backlog; */ 5, + /* rsync_port; */ 0, + }, + + /* ==== local_vars ==== */ + { + /* auth_users; */ NULL, + /* charset; */ NULL, + /* comment; */ NULL, + /* dont_compress; */ DEFAULT_DONT_COMPRESS, + /* exclude; */ NULL, + /* exclude_from; */ NULL, + /* filter; */ NULL, + /* gid; */ NULL, + /* hosts_allow; */ NULL, + /* hosts_deny; */ NULL, + /* include; */ NULL, + /* include_from; */ NULL, + /* incoming_chmod; */ NULL, + /* lock_file; */ DEFAULT_LOCK_FILE, + /* log_file; */ NULL, + /* log_format; */ "%o %h [%a] %m (%u) %f %l", + /* name; */ NULL, + /* outgoing_chmod; */ NULL, + /* path; */ NULL, + /* postxfer_exec; */ NULL, + /* prexfer_exec; */ NULL, + /* refuse_options; */ NULL, + /* secrets_file; */ NULL, + /* temp_dir; */ NULL, + /* uid; */ NULL, + + /* max_connections; */ 0, + /* max_verbosity; */ 1, + /* syslog_facility; */ LOG_DAEMON, + /* timeout; */ 0, + + /* fake_super; */ False, + /* forward_lookup; */ True, + /* ignore_errors; */ False, + /* ignore_nonreadable; */ False, + /* list; */ True, + /* munge_symlinks; */ (BOOL)-1, + /* numeric_ids; */ (BOOL)-1, + /* read_only; */ True, + /* reverse_lookup; */ True, + /* strict_modes; */ True, + /* transfer_logging; */ False, + /* use_chroot; */ True, + /* write_only; */ False, + } +}; + +/* The currently configured values for all the variables. */ +static all_vars Vars; + +/* Stack of "Vars" values used by the &include directive. */ +static item_list Vars_stack = EMPTY_ITEM_LIST; + +/* The array of section values that holds all the defined modules. */ +static item_list section_list = EMPTY_ITEM_LIST; + +static int iSectionIndex = -1; +static BOOL bInGlobalSection = True; + +#define NUMPARAMETERS (sizeof (parm_table) / sizeof (struct parm_struct)) + +static struct enum_list enum_facilities[] = { +#ifdef LOG_AUTH + { LOG_AUTH, "auth" }, +#endif +#ifdef LOG_AUTHPRIV + { LOG_AUTHPRIV, "authpriv" }, +#endif +#ifdef LOG_CRON + { LOG_CRON, "cron" }, +#endif +#ifdef LOG_DAEMON + { LOG_DAEMON, "daemon" }, +#endif +#ifdef LOG_FTP + { LOG_FTP, "ftp" }, +#endif +#ifdef LOG_KERN + { LOG_KERN, "kern" }, +#endif +#ifdef LOG_LPR + { LOG_LPR, "lpr" }, +#endif +#ifdef LOG_MAIL + { LOG_MAIL, "mail" }, +#endif +#ifdef LOG_NEWS + { LOG_NEWS, "news" }, +#endif +#ifdef LOG_AUTH + { LOG_AUTH, "security" }, +#endif +#ifdef LOG_SYSLOG + { LOG_SYSLOG, "syslog" }, +#endif +#ifdef LOG_USER + { LOG_USER, "user" }, +#endif +#ifdef LOG_UUCP + { LOG_UUCP, "uucp" }, +#endif +#ifdef LOG_LOCAL0 + { LOG_LOCAL0, "local0" }, +#endif +#ifdef LOG_LOCAL1 + { LOG_LOCAL1, "local1" }, +#endif +#ifdef LOG_LOCAL2 + { LOG_LOCAL2, "local2" }, +#endif +#ifdef LOG_LOCAL3 + { LOG_LOCAL3, "local3" }, +#endif +#ifdef LOG_LOCAL4 + { LOG_LOCAL4, "local4" }, +#endif +#ifdef LOG_LOCAL5 + { LOG_LOCAL5, "local5" }, +#endif +#ifdef LOG_LOCAL6 + { LOG_LOCAL6, "local6" }, +#endif +#ifdef LOG_LOCAL7 + { LOG_LOCAL7, "local7" }, +#endif + { -1, NULL } +}; + +static struct parm_struct parm_table[] = +{ + {"address", P_STRING, P_GLOBAL,&Vars.g.bind_address, NULL,0}, + {"listen backlog", P_INTEGER,P_GLOBAL,&Vars.g.listen_backlog, NULL,0}, + {"motd file", P_STRING, P_GLOBAL,&Vars.g.motd_file, NULL,0}, + {"pid file", P_STRING, P_GLOBAL,&Vars.g.pid_file, NULL,0}, + {"port", P_INTEGER,P_GLOBAL,&Vars.g.rsync_port, NULL,0}, + {"socket options", P_STRING, P_GLOBAL,&Vars.g.socket_options, NULL,0}, + + {"auth users", P_STRING, P_LOCAL, &Vars.l.auth_users, NULL,0}, + {"charset", P_STRING, P_LOCAL, &Vars.l.charset, NULL,0}, + {"comment", P_STRING, P_LOCAL, &Vars.l.comment, NULL,0}, + {"dont compress", P_STRING, P_LOCAL, &Vars.l.dont_compress, NULL,0}, + {"exclude from", P_STRING, P_LOCAL, &Vars.l.exclude_from, NULL,0}, + {"exclude", P_STRING, P_LOCAL, &Vars.l.exclude, NULL,0}, + {"fake super", P_BOOL, P_LOCAL, &Vars.l.fake_super, NULL,0}, + {"filter", P_STRING, P_LOCAL, &Vars.l.filter, NULL,0}, + {"forward lookup", P_BOOL, P_LOCAL, &Vars.l.forward_lookup, NULL,0}, + {"gid", P_STRING, P_LOCAL, &Vars.l.gid, NULL,0}, + {"hosts allow", P_STRING, P_LOCAL, &Vars.l.hosts_allow, NULL,0}, + {"hosts deny", P_STRING, P_LOCAL, &Vars.l.hosts_deny, NULL,0}, + {"ignore errors", P_BOOL, P_LOCAL, &Vars.l.ignore_errors, NULL,0}, + {"ignore nonreadable",P_BOOL, P_LOCAL, &Vars.l.ignore_nonreadable, NULL,0}, + {"include from", P_STRING, P_LOCAL, &Vars.l.include_from, NULL,0}, + {"include", P_STRING, P_LOCAL, &Vars.l.include, NULL,0}, + {"incoming chmod", P_STRING, P_LOCAL, &Vars.l.incoming_chmod, NULL,0}, + {"list", P_BOOL, P_LOCAL, &Vars.l.list, NULL,0}, + {"lock file", P_STRING, P_LOCAL, &Vars.l.lock_file, NULL,0}, + {"log file", P_STRING, P_LOCAL, &Vars.l.log_file, NULL,0}, + {"log format", P_STRING, P_LOCAL, &Vars.l.log_format, NULL,0}, + {"max connections", P_INTEGER,P_LOCAL, &Vars.l.max_connections, NULL,0}, + {"max verbosity", P_INTEGER,P_LOCAL, &Vars.l.max_verbosity, NULL,0}, + {"munge symlinks", P_BOOL, P_LOCAL, &Vars.l.munge_symlinks, NULL,0}, + {"name", P_STRING, P_LOCAL, &Vars.l.name, NULL,0}, + {"numeric ids", P_BOOL, P_LOCAL, &Vars.l.numeric_ids, NULL,0}, + {"outgoing chmod", P_STRING, P_LOCAL, &Vars.l.outgoing_chmod, NULL,0}, + {"path", P_PATH, P_LOCAL, &Vars.l.path, NULL,0}, +#ifdef HAVE_PUTENV + {"post-xfer exec", P_STRING, P_LOCAL, &Vars.l.postxfer_exec, NULL,0}, + {"pre-xfer exec", P_STRING, P_LOCAL, &Vars.l.prexfer_exec, NULL,0}, +#endif + {"read only", P_BOOL, P_LOCAL, &Vars.l.read_only, NULL,0}, + {"refuse options", P_STRING, P_LOCAL, &Vars.l.refuse_options, NULL,0}, + {"reverse lookup", P_BOOL, P_LOCAL, &Vars.l.reverse_lookup, NULL,0}, + {"secrets file", P_STRING, P_LOCAL, &Vars.l.secrets_file, NULL,0}, + {"strict modes", P_BOOL, P_LOCAL, &Vars.l.strict_modes, NULL,0}, + {"syslog facility", P_ENUM, P_LOCAL, &Vars.l.syslog_facility, enum_facilities,0}, + {"temp dir", P_PATH, P_LOCAL, &Vars.l.temp_dir, NULL,0}, + {"timeout", P_INTEGER,P_LOCAL, &Vars.l.timeout, NULL,0}, + {"transfer logging", P_BOOL, P_LOCAL, &Vars.l.transfer_logging, NULL,0}, + {"uid", P_STRING, P_LOCAL, &Vars.l.uid, NULL,0}, + {"use chroot", P_BOOL, P_LOCAL, &Vars.l.use_chroot, NULL,0}, + {"write only", P_BOOL, P_LOCAL, &Vars.l.write_only, NULL,0}, + {NULL, P_BOOL, P_NONE, NULL, NULL,0} +}; + +/* Initialise the Default all_vars structure. */ +static void reset_all_vars(void) +{ + memcpy(&Vars, &Defaults, sizeof Vars); +} + +/* Expand %VAR% references. Any unknown vars or unrecognized + * syntax leaves the raw chars unchanged. */ +static char *expand_vars(char *str) +{ + char *buf, *t, *f; + int bufsize; + + if (strchr(str, '%') == NULL) + return str; + + bufsize = strlen(str) + 2048; + if ((buf = new_array(char, bufsize+1)) == NULL) /* +1 for trailing '\0' */ + out_of_memory("expand_vars"); + + for (t = buf, f = str; bufsize && *f; ) { + if (*f == '%' && *++f != '%') { + char *percent = strchr(f, '%'); + if (percent) { + char *val; + *percent = '\0'; + val = getenv(f); + *percent = '%'; + if (val) { + int len = strlcpy(t, val, bufsize+1); + if (len > bufsize) + break; + bufsize -= len; + t += len; + f = percent + 1; + continue; + } + } + f--; + } + *t++ = *f++; + bufsize--; + } + *t = '\0'; + + if (*f) { + rprintf(FLOG, "Overflowed buf in expand_vars() trying to expand: %s\n", str); + exit_cleanup(RERR_MALLOC); + } + + if (bufsize && (buf = realloc(buf, t - buf + 1)) == NULL) + out_of_memory("expand_vars"); + + return buf; +} + +/* In this section all the functions that are used to access the + * parameters from the rest of the program are defined. */ + +#define FN_GLOBAL_STRING(fn_name, ptr) \ + char *fn_name(void) {return expand_vars(*(char **)(ptr) ? *(char **)(ptr) : "");} +#define FN_GLOBAL_BOOL(fn_name, ptr) \ + BOOL fn_name(void) {return *(BOOL *)(ptr);} +#define FN_GLOBAL_CHAR(fn_name, ptr) \ + char fn_name(void) {return *(char *)(ptr);} +#define FN_GLOBAL_INTEGER(fn_name, ptr) \ + int fn_name(void) {return *(int *)(ptr);} + +#define FN_LOCAL_STRING(fn_name, val) \ + char *fn_name(int i) {return expand_vars(LP_SNUM_OK(i) && iSECTION(i).val ? iSECTION(i).val : Vars.l.val ? Vars.l.val : "");} +#define FN_LOCAL_BOOL(fn_name, val) \ + BOOL fn_name(int i) {return LP_SNUM_OK(i)? iSECTION(i).val : Vars.l.val;} +#define FN_LOCAL_CHAR(fn_name, val) \ + char fn_name(int i) {return LP_SNUM_OK(i)? iSECTION(i).val : Vars.l.val;} +#define FN_LOCAL_INTEGER(fn_name, val) \ + int fn_name(int i) {return LP_SNUM_OK(i)? iSECTION(i).val : Vars.l.val;} + +FN_GLOBAL_STRING(lp_bind_address, &Vars.g.bind_address) +FN_GLOBAL_STRING(lp_motd_file, &Vars.g.motd_file) +FN_GLOBAL_STRING(lp_pid_file, &Vars.g.pid_file) +FN_GLOBAL_STRING(lp_socket_options, &Vars.g.socket_options) + +FN_GLOBAL_INTEGER(lp_listen_backlog, &Vars.g.listen_backlog) +FN_GLOBAL_INTEGER(lp_rsync_port, &Vars.g.rsync_port) + +FN_LOCAL_STRING(lp_auth_users, auth_users) +FN_LOCAL_STRING(lp_charset, charset) +FN_LOCAL_STRING(lp_comment, comment) +FN_LOCAL_STRING(lp_dont_compress, dont_compress) +FN_LOCAL_STRING(lp_exclude, exclude) +FN_LOCAL_STRING(lp_exclude_from, exclude_from) +FN_LOCAL_STRING(lp_filter, filter) +FN_LOCAL_STRING(lp_gid, gid) +FN_LOCAL_STRING(lp_hosts_allow, hosts_allow) +FN_LOCAL_STRING(lp_hosts_deny, hosts_deny) +FN_LOCAL_STRING(lp_include, include) +FN_LOCAL_STRING(lp_include_from, include_from) +FN_LOCAL_STRING(lp_incoming_chmod, incoming_chmod) +FN_LOCAL_STRING(lp_lock_file, lock_file) +FN_LOCAL_STRING(lp_log_file, log_file) +FN_LOCAL_STRING(lp_log_format, log_format) +FN_LOCAL_STRING(lp_name, name) +FN_LOCAL_STRING(lp_outgoing_chmod, outgoing_chmod) +FN_LOCAL_STRING(lp_path, path) +FN_LOCAL_STRING(lp_postxfer_exec, postxfer_exec) +FN_LOCAL_STRING(lp_prexfer_exec, prexfer_exec) +FN_LOCAL_STRING(lp_refuse_options, refuse_options) +FN_LOCAL_STRING(lp_secrets_file, secrets_file) +FN_LOCAL_STRING(lp_temp_dir, temp_dir) +FN_LOCAL_STRING(lp_uid, uid) + +FN_LOCAL_INTEGER(lp_max_connections, max_connections) +FN_LOCAL_INTEGER(lp_max_verbosity, max_verbosity) +FN_LOCAL_INTEGER(lp_syslog_facility, syslog_facility) +FN_LOCAL_INTEGER(lp_timeout, timeout) + +FN_LOCAL_BOOL(lp_fake_super, fake_super) +FN_LOCAL_BOOL(lp_forward_lookup, forward_lookup) +FN_LOCAL_BOOL(lp_ignore_errors, ignore_errors) +FN_LOCAL_BOOL(lp_ignore_nonreadable, ignore_nonreadable) +FN_LOCAL_BOOL(lp_list, list) +FN_LOCAL_BOOL(lp_munge_symlinks, munge_symlinks) +FN_LOCAL_BOOL(lp_numeric_ids, numeric_ids) +FN_LOCAL_BOOL(lp_read_only, read_only) +FN_LOCAL_BOOL(lp_reverse_lookup, reverse_lookup) +FN_LOCAL_BOOL(lp_strict_modes, strict_modes) +FN_LOCAL_BOOL(lp_transfer_logging, transfer_logging) +FN_LOCAL_BOOL(lp_use_chroot, use_chroot) +FN_LOCAL_BOOL(lp_write_only, write_only) + +/* Assign a copy of v to *s. Handles NULL strings. We don't worry + * about overwriting a malloc'd string because the long-running + * (port-listening) daemon only loads the config file once, and the + * per-job (forked or xinitd-ran) daemon only re-reads the file at + * the start, so any lost memory is inconsequential. */ +static inline void string_set(char **s, const char *v) +{ + if (!v) + *s = NULL; + else if (!(*s = strdup(v))) + out_of_memory("string_set"); +} + +/* Copy the local_vars, strdup'ing any strings. NOTE: this depends on + * the structure starting with a contiguous list of the char* variables, + * and having an accurate count in the LOCAL_STRING_COUNT() macro. */ +static void copy_section(local_vars *psectionDest, local_vars *psectionSource) +{ + int count = LOCAL_STRING_COUNT(); + char **strings = (char**)psectionDest; + + memcpy(psectionDest, psectionSource, sizeof psectionDest[0]); + while (count--) { + if (strings[count] && !(strings[count] = strdup(strings[count]))) + out_of_memory("copy_section"); + } +} + +/* Initialise a section to the defaults. */ +static void init_section(local_vars *psection) +{ + memset(psection, 0, sizeof (local_vars)); + copy_section(psection, &Vars.l); +} + +/* Do a case-insensitive, whitespace-ignoring string compare. */ +static int strwicmp(char *psz1, char *psz2) +{ + /* if BOTH strings are NULL, return TRUE, if ONE is NULL return */ + /* appropriate value. */ + if (psz1 == psz2) + return 0; + + if (psz1 == NULL) + return -1; + + if (psz2 == NULL) + return 1; + + /* sync the strings on first non-whitespace */ + while (1) { + while (isSpace(psz1)) + psz1++; + while (isSpace(psz2)) + psz2++; + if (toUpper(psz1) != toUpper(psz2) || *psz1 == '\0' || *psz2 == '\0') + break; + psz1++; + psz2++; + } + return *psz1 - *psz2; +} + +/* Find a section by name. Otherwise works like get_section. */ +static int getsectionbyname(char *name) +{ + int i; + + for (i = section_list.count - 1; i >= 0; i--) { + if (strwicmp(iSECTION(i).name, name) == 0) + break; + } + + return i; +} + +/* Add a new section to the sections array w/the default values. */ +static int add_a_section(char *name) +{ + int i; + local_vars *s; + + /* it might already exist */ + if (name) { + i = getsectionbyname(name); + if (i >= 0) + return i; + } + + i = section_list.count; + s = EXPAND_ITEM_LIST(§ion_list, local_vars, 2); + + init_section(s); + if (name) + string_set(&s->name, name); + + return i; +} + +/* Map a parameter's string representation to something we can use. + * Returns False if the parameter string is not recognised, else TRUE. */ +static int map_parameter(char *parmname) +{ + int iIndex; + + if (*parmname == '-') + return -1; + + for (iIndex = 0; parm_table[iIndex].label; iIndex++) { + if (strwicmp(parm_table[iIndex].label, parmname) == 0) + return iIndex; + } + + rprintf(FLOG, "Unknown Parameter encountered: \"%s\"\n", parmname); + return -1; +} + +/* Set a boolean variable from the text value stored in the passed string. + * Returns True in success, False if the passed string does not correctly + * represent a boolean. */ +static BOOL set_boolean(BOOL *pb, char *parmvalue) +{ + if (strwicmp(parmvalue, "yes") == 0 + || strwicmp(parmvalue, "true") == 0 + || strwicmp(parmvalue, "1") == 0) + *pb = True; + else if (strwicmp(parmvalue, "no") == 0 + || strwicmp(parmvalue, "False") == 0 + || strwicmp(parmvalue, "0") == 0) + *pb = False; + else { + rprintf(FLOG, "Badly formed boolean in configuration file: \"%s\".\n", parmvalue); + return False; + } + return True; +} + +/* Process a parameter. */ +static BOOL do_parameter(char *parmname, char *parmvalue) +{ + int parmnum, i; + void *parm_ptr; /* where we are going to store the result */ + void *def_ptr; + char *cp; + + parmnum = map_parameter(parmname); + + if (parmnum < 0) { + rprintf(FLOG, "IGNORING unknown parameter \"%s\"\n", parmname); + return True; + } + + def_ptr = parm_table[parmnum].ptr; + + if (bInGlobalSection) + parm_ptr = def_ptr; + else { + if (parm_table[parmnum].class == P_GLOBAL) { + rprintf(FLOG, "Global parameter %s found in module section!\n", parmname); + return True; + } + parm_ptr = SECTION_PTR(&iSECTION(iSectionIndex), def_ptr); + } + + /* now switch on the type of variable it is */ + switch (parm_table[parmnum].type) { + case P_PATH: + case P_STRING: + /* delay expansion of vars */ + break; + default: + /* expand any %VARS% now */ + parmvalue = expand_vars(parmvalue); + break; + } + + switch (parm_table[parmnum].type) { + case P_BOOL: + set_boolean(parm_ptr, parmvalue); + break; + + case P_BOOLREV: + set_boolean(parm_ptr, parmvalue); + *(BOOL *)parm_ptr = ! *(BOOL *)parm_ptr; + break; + + case P_INTEGER: + *(int *)parm_ptr = atoi(parmvalue); + break; + + case P_CHAR: + *(char *)parm_ptr = *parmvalue; + break; + + case P_OCTAL: + sscanf(parmvalue, "%o", (int *)parm_ptr); + break; + + case P_PATH: + string_set(parm_ptr, parmvalue); + if ((cp = *(char**)parm_ptr) != NULL) { + int len = strlen(cp); + while (len > 1 && cp[len-1] == '/') len--; + cp[len] = '\0'; + } + break; + + case P_STRING: + string_set(parm_ptr, parmvalue); + break; + + case P_ENUM: + for (i=0; parm_table[parmnum].enum_list[i].name; i++) { + if (strequal(parmvalue, parm_table[parmnum].enum_list[i].name)) { + *(int *)parm_ptr = parm_table[parmnum].enum_list[i].value; + break; + } + } + if (!parm_table[parmnum].enum_list[i].name) { + if (atoi(parmvalue) > 0) + *(int *)parm_ptr = atoi(parmvalue); + } + break; + } + + return True; +} + +/* Process a new section (rsync module). + * Returns True on success, False on failure. */ +static BOOL do_section(char *sectionname) +{ + BOOL isglobal; + + if (*sectionname == ']') { /* A special push/pop/reset directive from params.c */ + bInGlobalSection = 1; + if (strcmp(sectionname+1, "push") == 0) { + all_vars *vp = EXPAND_ITEM_LIST(&Vars_stack, all_vars, 2); + memcpy(vp, &Vars, sizeof Vars); + } else if (strcmp(sectionname+1, "pop") == 0 + || strcmp(sectionname+1, "reset") == 0) { + all_vars *vp = ((all_vars*)Vars_stack.items) + Vars_stack.count - 1; + if (!Vars_stack.count) + return False; + memcpy(&Vars, vp, sizeof Vars); + if (sectionname[1] == 'p') + Vars_stack.count--; + } else + return False; + return True; + } + + isglobal = strwicmp(sectionname, GLOBAL_NAME) == 0; + + /* At the end of the global section, add any --dparam items. */ + if (bInGlobalSection && !isglobal) { + if (!section_list.count) + set_dparams(0); + } + + /* if we've just struck a global section, note the fact. */ + bInGlobalSection = isglobal; + + /* check for multiple global sections */ + if (bInGlobalSection) + return True; + +#if 0 + /* If we have a current section, tidy it up before moving on. */ + if (iSectionIndex >= 0) { + /* Add any tidy work as needed ... */ + if (problem) + return False; + } +#endif + + if (strchr(sectionname, '/') != NULL) { + rprintf(FLOG, "Warning: invalid section name in configuration file: %s\n", sectionname); + return False; + } + + if ((iSectionIndex = add_a_section(sectionname)) < 0) { + rprintf(FLOG, "Failed to add a new module\n"); + bInGlobalSection = True; + return False; + } + + return True; +} + +/* Load the modules from the config file. Return True on success, + * False on failure. */ +int lp_load(char *pszFname, int globals_only) +{ + bInGlobalSection = True; + + reset_all_vars(); + + /* We get sections first, so have to start 'behind' to make up. */ + iSectionIndex = -1; + return pm_process(pszFname, globals_only ? NULL : do_section, do_parameter); +} + +BOOL set_dparams(int syntax_check_only) +{ + char *equal, *val, **params = dparam_list.items; + unsigned j; + + for (j = 0; j < dparam_list.count; j++) { + equal = strchr(params[j], '='); /* options.c verified this */ + *equal = '\0'; + if (syntax_check_only) { + if (map_parameter(params[j]) < 0) { + rprintf(FERROR, "Unknown parameter \"%s\"\n", params[j]); + *equal = '='; + return False; + } + } else { + for (val = equal+1; isSpace(val); val++) {} + do_parameter(params[j], val); + } + *equal = '='; + } + + return True; +} + +/* Return the max number of modules (sections). */ +int lp_num_modules(void) +{ + return section_list.count; +} + +/* Return the number of the module with the given name, or -1 if it doesn't + * exist. Note that this is a DIFFERENT ANIMAL from the internal function + * getsectionbyname()! This works ONLY if all sections have been loaded, + * and does not copy the found section. */ +int lp_number(char *name) +{ + int i; + + for (i = section_list.count - 1; i >= 0; i--) { + if (strcmp(lp_name(i), name) == 0) + break; + } + + return i; +} diff --git a/rsync/log.c b/rsync/log.c new file mode 100644 index 0000000..6e87a5b --- /dev/null +++ b/rsync/log.c @@ -0,0 +1,896 @@ +/* + * Logging and utility functions. + * + * Copyright (C) 1998-2001 Andrew Tridgell + * Copyright (C) 2000-2001 Martin Pool + * Copyright (C) 2003-2014 Wayne Davison + * + * 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, visit the http://fsf.org website. + */ + +#include "rsync.h" +#include "itypes.h" +#include "inums.h" + +extern int dry_run; +extern int am_daemon; +extern int am_server; +extern int am_sender; +extern int am_generator; +extern int local_server; +extern int quiet; +extern int module_id; +extern int checksum_len; +extern int allow_8bit_chars; +extern int protocol_version; +extern int always_checksum; +extern int preserve_times; +extern int msgs2stderr; +extern int stdout_format_has_i; +extern int stdout_format_has_o_or_i; +extern int logfile_format_has_i; +extern int logfile_format_has_o_or_i; +extern int receiver_symlink_times; +extern int64 total_data_written; +extern int64 total_data_read; +extern mode_t orig_umask; +extern char *auth_user; +extern char *stdout_format; +extern char *logfile_format; +extern char *logfile_name; +#ifdef ICONV_CONST +extern iconv_t ic_chck; +#endif +#ifdef ICONV_OPTION +extern iconv_t ic_recv; +#endif +extern char curr_dir[MAXPATHLEN]; +extern char *full_module_path; +extern unsigned int module_dirlen; +extern char sender_file_sum[MAX_DIGEST_LEN]; +extern const char undetermined_hostname[]; + +static int log_initialised; +static int logfile_was_closed; +static FILE *logfile_fp; +struct stats stats; + +int got_xfer_error = 0; +int output_needs_newline = 0; +int send_msgs_to_gen = 0; + +static int64 initial_data_written; +static int64 initial_data_read; + +struct { + int code; + char const *name; +} const rerr_names[] = { + { RERR_SYNTAX , "syntax or usage error" }, + { RERR_PROTOCOL , "protocol incompatibility" }, + { RERR_FILESELECT , "errors selecting input/output files, dirs" }, + { RERR_UNSUPPORTED, "requested action not supported" }, + { RERR_STARTCLIENT, "error starting client-server protocol" }, + { RERR_SOCKETIO , "error in socket IO" }, + { RERR_FILEIO , "error in file IO" }, + { RERR_STREAMIO , "error in rsync protocol data stream" }, + { RERR_MESSAGEIO , "errors with program diagnostics" }, + { RERR_IPC , "error in IPC code" }, + { RERR_CRASHED , "sibling process crashed" }, + { RERR_TERMINATED , "sibling process terminated abnormally" }, + { RERR_SIGNAL1 , "received SIGUSR1" }, + { RERR_SIGNAL , "received SIGINT, SIGTERM, or SIGHUP" }, + { RERR_WAITCHILD , "waitpid() failed" }, + { RERR_MALLOC , "error allocating core memory buffers" }, + { RERR_PARTIAL , "some files/attrs were not transferred (see previous errors)" }, + { RERR_VANISHED , "some files vanished before they could be transferred" }, + { RERR_DEL_LIMIT , "the --max-delete limit stopped deletions" }, + { RERR_TIMEOUT , "timeout in data send/receive" }, + { RERR_CONTIMEOUT , "timeout waiting for daemon connection" }, + { RERR_CMD_FAILED , "remote shell failed" }, + { RERR_CMD_KILLED , "remote shell killed" }, + { RERR_CMD_RUN , "remote command could not be run" }, + { RERR_CMD_NOTFOUND,"remote command not found" }, + { 0, NULL } +}; + +/* + * Map from rsync error code to name, or return NULL. + */ +static char const *rerr_name(int code) +{ + int i; + for (i = 0; rerr_names[i].name; i++) { + if (rerr_names[i].code == code) + return rerr_names[i].name; + } + return NULL; +} + +static void logit(int priority, const char *buf) +{ + if (logfile_was_closed) + logfile_reopen(); + if (logfile_fp) { + fprintf(logfile_fp, "%s [%d] %s", timestring(time(NULL)), (int)getpid(), buf); + fflush(logfile_fp); + } else { + syslog(priority, "%s", buf); + } +} + +static void syslog_init() +{ + static int been_here = 0; + int options = LOG_PID; + + if (been_here) + return; + been_here = 1; + +#ifdef LOG_NDELAY + options |= LOG_NDELAY; +#endif + +#ifdef LOG_DAEMON + openlog("rsyncd", options, lp_syslog_facility(module_id)); +#else + openlog("rsyncd", options); +#endif + +#ifndef LOG_NDELAY + logit(LOG_INFO, "rsyncd started\n"); +#endif +} + +static void logfile_open(void) +{ + mode_t old_umask = umask(022 | orig_umask); + logfile_fp = fopen(logfile_name, "a"); + umask(old_umask); + if (!logfile_fp) { + int fopen_errno = errno; + /* Rsync falls back to using syslog on failure. */ + syslog_init(); + rsyserr(FERROR, fopen_errno, + "failed to open log-file %s", logfile_name); + rprintf(FINFO, "Ignoring \"log file\" setting.\n"); + } +} + +void log_init(int restart) +{ + if (log_initialised) { + if (!restart) + return; + if (strcmp(logfile_name, lp_log_file(module_id)) != 0) { + if (logfile_fp) { + fclose(logfile_fp); + logfile_fp = NULL; + } else + closelog(); + logfile_name = NULL; + } else if (*logfile_name) + return; /* unchanged, non-empty "log file" names */ + else if (lp_syslog_facility(-1) != lp_syslog_facility(module_id)) + closelog(); + else + return; /* unchanged syslog settings */ + } else + log_initialised = 1; + + /* This looks pointless, but it is needed in order for the + * C library on some systems to fetch the timezone info + * before the chroot. */ + timestring(time(NULL)); + + /* Optionally use a log file instead of syslog. (Non-daemon + * rsyncs will have already set logfile_name, as needed.) */ + if (am_daemon && !logfile_name) + logfile_name = lp_log_file(module_id); + if (logfile_name && *logfile_name) + logfile_open(); + else + syslog_init(); +} + +void logfile_close(void) +{ + if (logfile_fp) { + logfile_was_closed = 1; + fclose(logfile_fp); + logfile_fp = NULL; + } +} + +void logfile_reopen(void) +{ + if (logfile_was_closed) { + logfile_was_closed = 0; + logfile_open(); + } +} + +static void filtered_fwrite(FILE *f, const char *buf, int len, int use_isprint) +{ + const char *s, *end = buf + len; + for (s = buf; s < end; s++) { + if ((s < end - 4 + && *s == '\\' && s[1] == '#' + && isDigit(s + 2) + && isDigit(s + 3) + && isDigit(s + 4)) + || (*s != '\t' + && ((use_isprint && !isPrint(s)) + || *(uchar*)s < ' '))) { + if (s != buf && fwrite(buf, s - buf, 1, f) != 1) + exit_cleanup(RERR_MESSAGEIO); + fprintf(f, "\\#%03o", *(uchar*)s); + buf = s + 1; + } + } + if (buf != end && fwrite(buf, end - buf, 1, f) != 1) + exit_cleanup(RERR_MESSAGEIO); +} + +/* this is the underlying (unformatted) rsync debugging function. Call + * it with FINFO, FERROR_*, FWARNING, FLOG, or FCLIENT. Note: recursion + * can happen with certain fatal conditions. */ +void rwrite(enum logcode code, const char *buf, int len, int is_utf8) +{ + int trailing_CR_or_NL; + FILE *f = msgs2stderr ? stderr : stdout; +#ifdef ICONV_OPTION + iconv_t ic = is_utf8 && ic_recv != (iconv_t)-1 ? ic_recv : ic_chck; +#else +#ifdef ICONV_CONST + iconv_t ic = ic_chck; +#endif +#endif + + if (len < 0) + exit_cleanup(RERR_MESSAGEIO); + + if (msgs2stderr) { + if (!am_daemon) { + if (code == FLOG) + return; + goto output_msg; + } + if (code == FCLIENT) + return; + code = FLOG; + } else if (send_msgs_to_gen) { + assert(!is_utf8); + /* Pass the message to our sibling in native charset. */ + send_msg((enum msgcode)code, buf, len, 0); + return; + } + + if (code == FERROR_SOCKET) /* This gets simplified for a non-sibling. */ + code = FERROR; + else if (code == FERROR_UTF8) { + is_utf8 = 1; + code = FERROR; + } + + if (code == FCLIENT) + code = FINFO; + else if (am_daemon || logfile_name) { + static int in_block; + char msg[2048]; + int priority = code == FINFO || code == FLOG ? LOG_INFO : LOG_WARNING; + + if (in_block) + return; + in_block = 1; + if (!log_initialised) + log_init(0); + strlcpy(msg, buf, MIN((int)sizeof msg, len + 1)); + logit(priority, msg); + in_block = 0; + + if (code == FLOG || (am_daemon && !am_server)) + return; + } else if (code == FLOG) + return; + + if (quiet && code == FINFO) + return; + + if (am_server) { + enum msgcode msg = (enum msgcode)code; + if (protocol_version < 30) { + if (msg == MSG_ERROR) + msg = MSG_ERROR_XFER; + else if (msg == MSG_WARNING) + msg = MSG_INFO; + } + /* Pass the message to the non-server side. */ + if (send_msg(msg, buf, len, !is_utf8)) + return; + if (am_daemon) { + /* TODO: can we send the error to the user somehow? */ + return; + } + f = stderr; + } + +output_msg: + switch (code) { + case FERROR_XFER: + got_xfer_error = 1; + /* FALL THROUGH */ + case FERROR: + case FERROR_UTF8: + case FERROR_SOCKET: + case FWARNING: + f = stderr; + break; + case FLOG: + case FINFO: + case FCLIENT: + break; + default: + fprintf(stderr, "Unknown logcode in rwrite(): %d [%s]\n", (int)code, who_am_i()); + exit_cleanup(RERR_MESSAGEIO); + } + + if (output_needs_newline) { + fputc('\n', f); + output_needs_newline = 0; + } + + trailing_CR_or_NL = len && (buf[len-1] == '\n' || buf[len-1] == '\r') + ? buf[--len] : 0; + + if (len && buf[0] == '\r') { + fputc('\r', f); + buf++; + len--; + } + +#ifdef ICONV_CONST + if (ic != (iconv_t)-1) { + xbuf outbuf, inbuf; + char convbuf[1024]; + int ierrno; + + INIT_CONST_XBUF(outbuf, convbuf); + INIT_XBUF(inbuf, (char*)buf, len, (size_t)-1); + + while (inbuf.len) { + iconvbufs(ic, &inbuf, &outbuf, inbuf.pos ? 0 : ICB_INIT); + ierrno = errno; + if (outbuf.len) { + filtered_fwrite(f, convbuf, outbuf.len, 0); + outbuf.len = 0; + } + if (!ierrno || ierrno == E2BIG) + continue; + fprintf(f, "\\#%03o", CVAL(inbuf.buf, inbuf.pos++)); + inbuf.len--; + } + } else +#endif + filtered_fwrite(f, buf, len, !allow_8bit_chars); + + if (trailing_CR_or_NL) { + fputc(trailing_CR_or_NL, f); + fflush(f); + } +} + +/* This is the rsync debugging function. Call it with FINFO, FERROR_*, + * FWARNING, FLOG, or FCLIENT. */ +void rprintf(enum logcode code, const char *format, ...) +{ + va_list ap; + char buf[BIGPATHBUFLEN]; + size_t len; + + va_start(ap, format); + len = vsnprintf(buf, sizeof buf, format, ap); + va_end(ap); + + /* Deal with buffer overruns. Instead of panicking, just + * truncate the resulting string. (Note that configure ensures + * that we have a vsnprintf() that doesn't ever return -1.) */ + if (len > sizeof buf - 1) { + static const char ellipsis[] = "[...]"; + + /* Reset length, and zero-terminate the end of our buffer */ + len = sizeof buf - 1; + buf[len] = '\0'; + + /* Copy the ellipsis to the end of the string, but give + * us one extra character: + * + * v--- null byte at buf[sizeof buf - 1] + * abcdefghij0 + * -> abcd[...]00 <-- now two null bytes at end + * + * If the input format string has a trailing newline, + * we copy it into that extra null; if it doesn't, well, + * all we lose is one byte. */ + memcpy(buf+len-sizeof ellipsis, ellipsis, sizeof ellipsis); + if (format[strlen(format)-1] == '\n') { + buf[len-1] = '\n'; + } + } + + rwrite(code, buf, len, 0); +} + +/* This is like rprintf, but it also tries to print some + * representation of the error code. Normally errcode = errno. + * + * Unlike rprintf, this always adds a newline and there should not be + * one in the format string. + * + * Note that since strerror might involve dynamically loading a + * message catalog we need to call it once before chroot-ing. */ +void rsyserr(enum logcode code, int errcode, const char *format, ...) +{ + va_list ap; + char buf[BIGPATHBUFLEN]; + size_t len; + + strlcpy(buf, RSYNC_NAME ": ", sizeof buf); + len = (sizeof RSYNC_NAME ": ") - 1; + + va_start(ap, format); + len += vsnprintf(buf + len, sizeof buf - len, format, ap); + va_end(ap); + + if (len < sizeof buf) { + len += snprintf(buf + len, sizeof buf - len, + ": %s (%d)\n", strerror(errcode), errcode); + } + if (len >= sizeof buf) + exit_cleanup(RERR_MESSAGEIO); + + rwrite(code, buf, len, 0); +} + +void rflush(enum logcode code) +{ + FILE *f; + + if (am_daemon || code == FLOG) + return; + + if (!am_server && (code == FINFO || code == FCLIENT)) + f = stdout; + else + f = stderr; + + fflush(f); +} + +void remember_initial_stats(void) +{ + initial_data_read = total_data_read; + initial_data_written = total_data_written; +} + +/* A generic logging routine for send/recv, with parameter substitiution. */ +static void log_formatted(enum logcode code, const char *format, const char *op, + struct file_struct *file, const char *fname, int iflags, + const char *hlink) +{ + char buf[MAXPATHLEN+1024], buf2[MAXPATHLEN], fmt[32]; + char *p, *s, *c; + const char *n; + size_t len, total; + int64 b; + + *fmt = '%'; + + /* We expand % codes one by one in place in buf. We don't + * copy in the terminating null of the inserted strings, but + * rather keep going until we reach the null of the format. */ + total = strlcpy(buf, format, sizeof buf); + if (total > MAXPATHLEN) { + rprintf(FERROR, "log-format string is WAY too long!\n"); + exit_cleanup(RERR_MESSAGEIO); + } + buf[total++] = '\n'; + buf[total] = '\0'; + + for (p = buf; (p = strchr(p, '%')) != NULL; ) { + int humanize = 0; + s = p++; + c = fmt + 1; + while (*p == '\'') { + humanize++; + p++; + } + if (*p == '-') + *c++ = *p++; + while (isDigit(p) && c - fmt < (int)(sizeof fmt) - 8) + *c++ = *p++; + while (*p == '\'') { + humanize++; + p++; + } + if (!*p) + break; + *c = '\0'; + n = NULL; + + /* Note for %h and %a: it doesn't matter what fd we pass to + * client_{name,addr} because rsync_module will already have + * forced the answer to be cached (assuming, of course, for %h + * that lp_reverse_lookup(module_id) is true). */ + switch (*p) { + case 'h': + if (am_daemon) { + n = lp_reverse_lookup(module_id) + ? client_name(0) : undetermined_hostname; + } + break; + case 'a': + if (am_daemon) + n = client_addr(0); + break; + case 'l': + strlcat(fmt, "s", sizeof fmt); + snprintf(buf2, sizeof buf2, fmt, + do_big_num(F_LENGTH(file), humanize, NULL)); + n = buf2; + break; + case 'U': + strlcat(fmt, "u", sizeof fmt); + snprintf(buf2, sizeof buf2, fmt, + uid_ndx ? F_OWNER(file) : 0); + n = buf2; + break; + case 'G': + if (!gid_ndx || file->flags & FLAG_SKIP_GROUP) + n = "DEFAULT"; + else { + strlcat(fmt, "u", sizeof fmt); + snprintf(buf2, sizeof buf2, fmt, + F_GROUP(file)); + n = buf2; + } + break; + case 'p': + strlcat(fmt, "d", sizeof fmt); + snprintf(buf2, sizeof buf2, fmt, (int)getpid()); + n = buf2; + break; + case 'M': + n = c = timestring(file->modtime); + while ((c = strchr(c, ' ')) != NULL) + *c = '-'; + break; + case 'B': + c = buf2 + MAXPATHLEN - PERMSTRING_SIZE - 1; + permstring(c, file->mode); + n = c + 1; /* skip the type char */ + break; + case 'o': + n = op; + break; + case 'f': + if (fname) { + c = f_name_buf(); + strlcpy(c, fname, MAXPATHLEN); + } else + c = f_name(file, NULL); + if (am_sender && F_PATHNAME(file)) { + pathjoin(buf2, sizeof buf2, + F_PATHNAME(file), c); + clean_fname(buf2, 0); + if (fmt[1]) { + strlcpy(c, buf2, MAXPATHLEN); + n = c; + } else + n = buf2; + } else if (am_daemon && *c != '/') { + pathjoin(buf2, sizeof buf2, + curr_dir + module_dirlen, c); + clean_fname(buf2, 0); + if (fmt[1]) { + strlcpy(c, buf2, MAXPATHLEN); + n = c; + } else + n = buf2; + } else { + clean_fname(c, 0); + n = c; + } + if (*n == '/') + n++; + break; + case 'n': + if (fname) { + c = f_name_buf(); + strlcpy(c, fname, MAXPATHLEN); + } else + c = f_name(file, NULL); + if (S_ISDIR(file->mode)) + strlcat(c, "/", MAXPATHLEN); + n = c; + break; + case 'L': + if (hlink && *hlink) { + n = hlink; + strlcpy(buf2, " => ", sizeof buf2); + } else if (S_ISLNK(file->mode) && !fname) { + n = F_SYMLINK(file); + strlcpy(buf2, " -> ", sizeof buf2); + } else { + n = ""; + if (!fmt[1]) + break; + strlcpy(buf2, " ", sizeof buf2); + } + strlcat(fmt, "s", sizeof fmt); + snprintf(buf2 + 4, sizeof buf2 - 4, fmt, n); + n = buf2; + break; + case 'm': + n = lp_name(module_id); + break; + case 't': + n = timestring(time(NULL)); + break; + case 'P': + n = full_module_path; + break; + case 'u': + n = auth_user; + break; + case 'b': + if (!(iflags & ITEM_TRANSFER)) + b = 0; + else if (am_sender) + b = total_data_written - initial_data_written; + else + b = total_data_read - initial_data_read; + strlcat(fmt, "s", sizeof fmt); + snprintf(buf2, sizeof buf2, fmt, + do_big_num(b, humanize, NULL)); + n = buf2; + break; + case 'c': + if (!(iflags & ITEM_TRANSFER)) + b = 0; + else if (!am_sender) + b = total_data_written - initial_data_written; + else + b = total_data_read - initial_data_read; + strlcat(fmt, "s", sizeof fmt); + snprintf(buf2, sizeof buf2, fmt, + do_big_num(b, humanize, NULL)); + n = buf2; + break; + case 'C': + if (protocol_version >= 30 + && (iflags & ITEM_TRANSFER + || (always_checksum && S_ISREG(file->mode)))) { + const char *sum = iflags & ITEM_TRANSFER + ? sender_file_sum : F_SUM(file); + n = sum_as_hex(sum); + } else { + memset(buf2, ' ', checksum_len*2); + buf2[checksum_len*2] = '\0'; + n = buf2; + } + break; + case 'i': + if (iflags & ITEM_DELETED) { + n = "*deleting "; + break; + } + n = c = buf2 + MAXPATHLEN - 32; + c[0] = iflags & ITEM_LOCAL_CHANGE + ? iflags & ITEM_XNAME_FOLLOWS ? 'h' : 'c' + : !(iflags & ITEM_TRANSFER) ? '.' + : !local_server && *op == 's' ? '<' : '>'; + if (S_ISLNK(file->mode)) { + c[1] = 'L'; + c[3] = '.'; + c[4] = !(iflags & ITEM_REPORT_TIME) ? '.' + : !preserve_times || !receiver_symlink_times + || (iflags & ITEM_REPORT_TIMEFAIL) ? 'T' : 't'; + } else { + c[1] = S_ISDIR(file->mode) ? 'd' + : IS_SPECIAL(file->mode) ? 'S' + : IS_DEVICE(file->mode) ? 'D' : 'f'; + c[3] = !(iflags & ITEM_REPORT_SIZE) ? '.' : 's'; + c[4] = !(iflags & ITEM_REPORT_TIME) ? '.' + : !preserve_times ? 'T' : 't'; + } + c[2] = !(iflags & ITEM_REPORT_CHANGE) ? '.' : 'c'; + c[5] = !(iflags & ITEM_REPORT_PERMS) ? '.' : 'p'; + c[6] = !(iflags & ITEM_REPORT_OWNER) ? '.' : 'o'; + c[7] = !(iflags & ITEM_REPORT_GROUP) ? '.' : 'g'; + c[8] = !(iflags & ITEM_REPORT_ATIME) ? '.' : 'u'; + c[9] = !(iflags & ITEM_REPORT_ACL) ? '.' : 'a'; + c[10] = !(iflags & ITEM_REPORT_XATTR) ? '.' : 'x'; + c[11] = '\0'; + + if (iflags & (ITEM_IS_NEW|ITEM_MISSING_DATA)) { + char ch = iflags & ITEM_IS_NEW ? '+' : '?'; + int i; + for (i = 2; c[i]; i++) + c[i] = ch; + } else if (c[0] == '.' || c[0] == 'h' || c[0] == 'c') { + int i; + for (i = 2; c[i]; i++) { + if (c[i] != '.') + break; + } + if (!c[i]) { + for (i = 2; c[i]; i++) + c[i] = ' '; + } + } + break; + } + + /* "n" is the string to be inserted in place of this % code. */ + if (!n) + continue; + if (n != buf2 && fmt[1]) { + strlcat(fmt, "s", sizeof fmt); + snprintf(buf2, sizeof buf2, fmt, n); + n = buf2; + } + len = strlen(n); + + /* Subtract the length of the escape from the string's size. */ + total -= p - s + 1; + + if (len + total >= (size_t)sizeof buf) { + rprintf(FERROR, + "buffer overflow expanding %%%c -- exiting\n", + p[0]); + exit_cleanup(RERR_MESSAGEIO); + } + + /* Shuffle the rest of the string along to make space for n */ + if (len != (size_t)(p - s + 1)) + memmove(s + len, p + 1, total - (s - buf) + 1); + total += len; + + /* Insert the contents of string "n", but NOT its null. */ + if (len) + memcpy(s, n, len); + + /* Skip over inserted string; continue looking */ + p = s + len; + } + + rwrite(code, buf, total, 0); +} + +/* Return 1 if the format escape is in the log-format string (e.g. look for + * the 'b' in the "%9b" format escape). */ +int log_format_has(const char *format, char esc) +{ + const char *p; + + if (!format) + return 0; + + for (p = format; (p = strchr(p, '%')) != NULL; ) { + for (p++; *p == '\''; p++) {} /*SHARED ITERATOR*/ + if (*p == '-') + p++; + while (isDigit(p)) + p++; + while (*p == '\'') p++; + if (!*p) + break; + if (*p == esc) + return 1; + } + return 0; +} + +/* Log the transfer of a file. If the code is FCLIENT, the output just goes + * to stdout. If it is FLOG, it just goes to the log file. Otherwise we + * output to both. */ +void log_item(enum logcode code, struct file_struct *file, int iflags, const char *hlink) +{ + const char *s_or_r = am_sender ? "send" : "recv"; + + if (code != FLOG && stdout_format && !am_server) + log_formatted(FCLIENT, stdout_format, s_or_r, file, NULL, iflags, hlink); + if (code != FCLIENT && logfile_format && *logfile_format) + log_formatted(FLOG, logfile_format, s_or_r, file, NULL, iflags, hlink); +} + +void maybe_log_item(struct file_struct *file, int iflags, int itemizing, + const char *buf) +{ + int significant_flags = iflags & SIGNIFICANT_ITEM_FLAGS; + int see_item = itemizing && (significant_flags || *buf + || stdout_format_has_i > 1 || (INFO_GTE(NAME, 2) && stdout_format_has_i)); + int local_change = iflags & ITEM_LOCAL_CHANGE && significant_flags; + if (am_server) { + if (logfile_name && !dry_run && see_item + && (significant_flags || logfile_format_has_i)) + log_item(FLOG, file, iflags, buf); + } else if (see_item || local_change || *buf + || (S_ISDIR(file->mode) && significant_flags)) { + enum logcode code = significant_flags || logfile_format_has_i ? FINFO : FCLIENT; + log_item(code, file, iflags, buf); + } +} + +void log_delete(const char *fname, int mode) +{ + static struct { + union file_extras ex[4]; /* just in case... */ + struct file_struct file; + } x; /* Zero-initialized due to static declaration. */ + int len = strlen(fname); + const char *fmt; + + x.file.mode = mode; + + if (am_server && protocol_version >= 29 && len < MAXPATHLEN) { + if (S_ISDIR(mode)) + len++; /* directories include trailing null */ + send_msg(MSG_DELETED, fname, len, am_generator); + } else if (!INFO_GTE(DEL, 1) && !stdout_format) + ; + else { + fmt = stdout_format_has_o_or_i ? stdout_format : "deleting %n"; + log_formatted(FCLIENT, fmt, "del.", &x.file, fname, ITEM_DELETED, NULL); + } + + if (!logfile_name || dry_run || !logfile_format) + return; + + fmt = logfile_format_has_o_or_i ? logfile_format : "deleting %n"; + log_formatted(FLOG, fmt, "del.", &x.file, fname, ITEM_DELETED, NULL); +} + +/* + * Called when the transfer is interrupted for some reason. + * + * Code is one of the RERR_* codes from errcode.h, or terminating + * successfully. + */ +void log_exit(int code, const char *file, int line) +{ + if (code == 0) { + rprintf(FLOG,"sent %s bytes received %s bytes total size %s\n", + big_num(stats.total_written), + big_num(stats.total_read), + big_num(stats.total_size)); + } else if (am_server != 2) { + const char *name; + + name = rerr_name(code); + if (!name) + name = "unexplained error"; + + /* VANISHED is not an error, only a warning */ + if (code == RERR_VANISHED) { + rprintf(FWARNING, "rsync warning: %s (code %d) at %s(%d) [%s=%s]\n", + name, code, file, line, who_am_i(), RSYNC_VERSION); + } else { + rprintf(FERROR, "rsync error: %s (code %d) at %s(%d) [%s=%s]\n", + name, code, file, line, who_am_i(), RSYNC_VERSION); + } + } +} diff --git a/rsync/main.c b/rsync/main.c new file mode 100644 index 0000000..e7a13f7 --- /dev/null +++ b/rsync/main.c @@ -0,0 +1,1640 @@ +/* + * The startup routines, including main(), for rsync. + * + * Copyright (C) 1996-2001 Andrew Tridgell + * Copyright (C) 1996 Paul Mackerras + * Copyright (C) 2001, 2002 Martin Pool + * Copyright (C) 2003-2014 Wayne Davison + * + * 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, visit the http://fsf.org website. + */ + +#include "rsync.h" +#include "inums.h" +#include "io.h" +#if defined CONFIG_LOCALE && defined HAVE_LOCALE_H +#include +#endif + +extern int dry_run; +extern int list_only; +extern int io_timeout; +extern int am_root; +extern int am_server; +extern int am_sender; +extern int am_daemon; +extern int inc_recurse; +extern int blocking_io; +extern int always_checksum; +extern int remove_source_files; +extern int output_needs_newline; +extern int need_messages_from_generator; +extern int kluge_around_eof; +extern int got_xfer_error; +extern int msgs2stderr; +extern int module_id; +extern int read_only; +extern int copy_links; +extern int copy_dirlinks; +extern int copy_unsafe_links; +extern int keep_dirlinks; +extern int preserve_hard_links; +extern int protocol_version; +extern int file_total; +extern int recurse; +extern int xfer_dirs; +extern int protect_args; +extern int relative_paths; +extern int sanitize_paths; +extern int curr_dir_depth; +extern int curr_dir_len; +extern int module_id; +extern int rsync_port; +extern int whole_file; +extern int read_batch; +extern int write_batch; +extern int batch_fd; +extern int sock_f_in; +extern int sock_f_out; +extern int filesfrom_fd; +extern int connect_timeout; +extern int send_msgs_to_gen; +extern dev_t filesystem_dev; +extern pid_t cleanup_child_pid; +extern size_t bwlimit_writemax; +extern unsigned int module_dirlen; +extern BOOL flist_receiving_enabled; +extern BOOL shutting_down; +extern int basis_dir_cnt; +extern struct stats stats; +extern char *stdout_format; +extern char *logfile_format; +extern char *filesfrom_host; +extern char *partial_dir; +extern char *dest_option; +extern char *rsync_path; +extern char *shell_cmd; +extern char *batch_name; +extern char *password_file; +extern char *backup_dir; +extern char curr_dir[MAXPATHLEN]; +extern char backup_dir_buf[MAXPATHLEN]; +extern char *basis_dir[MAX_BASIS_DIRS+1]; +extern struct file_list *first_flist; +extern filter_rule_list daemon_filter_list; + +uid_t our_uid; +gid_t our_gid; +int am_receiver = 0; /* Only set to 1 after the receiver/generator fork. */ +int am_generator = 0; /* Only set to 1 after the receiver/generator fork. */ +int local_server = 0; +int daemon_over_rsh = 0; +mode_t orig_umask = 0; +int batch_gen_fd = -1; +int sender_keeps_checksum = 0; + +/* There's probably never more than at most 2 outstanding child processes, + * but set it higher, just in case. */ +#define MAXCHILDPROCS 7 + +#ifdef HAVE_SIGACTION +# ifdef HAVE_SIGPROCMASK +# define SIGACTMASK(n,h) SIGACTION(n,h), sigaddset(&sigmask,(n)) +# else +# define SIGACTMASK(n,h) SIGACTION(n,h) +# endif +static struct sigaction sigact; +#endif + +struct pid_status { + pid_t pid; + int status; +} pid_stat_table[MAXCHILDPROCS]; + +static time_t starttime, endtime; +static int64 total_read, total_written; + +static void show_malloc_stats(void); + +/* Works like waitpid(), but if we already harvested the child pid in our + * remember_children(), we succeed instead of returning an error. */ +pid_t wait_process(pid_t pid, int *status_ptr, int flags) +{ + pid_t waited_pid; + + do { + waited_pid = waitpid(pid, status_ptr, flags); + } while (waited_pid == -1 && errno == EINTR); + + if (waited_pid == -1 && errno == ECHILD) { + /* Status of requested child no longer available: check to + * see if it was processed by remember_children(). */ + int cnt; + for (cnt = 0; cnt < MAXCHILDPROCS; cnt++) { + if (pid == pid_stat_table[cnt].pid) { + *status_ptr = pid_stat_table[cnt].status; + pid_stat_table[cnt].pid = 0; + return pid; + } + } + } + + return waited_pid; +} + +/* Wait for a process to exit, calling io_flush while waiting. */ +static void wait_process_with_flush(pid_t pid, int *exit_code_ptr) +{ + pid_t waited_pid; + int status; + + while ((waited_pid = wait_process(pid, &status, WNOHANG)) == 0) { + msleep(20); + io_flush(FULL_FLUSH); + } + + /* TODO: If the child exited on a signal, then log an + * appropriate error message. Perhaps we should also accept a + * message describing the purpose of the child. Also indicate + * this to the caller so that they know something went wrong. */ + if (waited_pid < 0) { + rsyserr(FERROR, errno, "waitpid"); + *exit_code_ptr = RERR_WAITCHILD; + } else if (!WIFEXITED(status)) { +#ifdef WCOREDUMP + if (WCOREDUMP(status)) + *exit_code_ptr = RERR_CRASHED; + else +#endif + if (WIFSIGNALED(status)) + *exit_code_ptr = RERR_TERMINATED; + else + *exit_code_ptr = RERR_WAITCHILD; + } else + *exit_code_ptr = WEXITSTATUS(status); +} + +void write_del_stats(int f) +{ + if (read_batch) + write_int(f, NDX_DEL_STATS); + else + write_ndx(f, NDX_DEL_STATS); + write_varint(f, stats.deleted_files - stats.deleted_dirs + - stats.deleted_symlinks - stats.deleted_devices + - stats.deleted_specials); + write_varint(f, stats.deleted_dirs); + write_varint(f, stats.deleted_symlinks); + write_varint(f, stats.deleted_devices); + write_varint(f, stats.deleted_specials); +} + +void read_del_stats(int f) +{ + stats.deleted_files = read_varint(f); + stats.deleted_files += stats.deleted_dirs = read_varint(f); + stats.deleted_files += stats.deleted_symlinks = read_varint(f); + stats.deleted_files += stats.deleted_devices = read_varint(f); + stats.deleted_files += stats.deleted_specials = read_varint(f); +} + +/* This function gets called from all 3 processes. We want the client side + * to actually output the text, but the sender is the only process that has + * all the stats we need. So, if we're a client sender, we do the report. + * If we're a server sender, we write the stats on the supplied fd. If + * we're the client receiver we read the stats from the supplied fd and do + * the report. All processes might also generate a set of debug stats, if + * the verbose level is high enough (this is the only thing that the + * generator process and the server receiver ever do here). */ +static void handle_stats(int f) +{ + endtime = time(NULL); + + /* Cache two stats because the read/write code can change it. */ + total_read = stats.total_read; + total_written = stats.total_written; + + if (INFO_GTE(STATS, 3)) { + /* These come out from every process */ + show_malloc_stats(); + show_flist_stats(); + } + + if (am_generator) + return; + + if (am_daemon) { + if (f == -1 || !am_sender) + return; + } + + if (am_server) { + if (am_sender) { + write_varlong30(f, total_read, 3); + write_varlong30(f, total_written, 3); + write_varlong30(f, stats.total_size, 3); + if (protocol_version >= 29) { + write_varlong30(f, stats.flist_buildtime, 3); + write_varlong30(f, stats.flist_xfertime, 3); + } + } + return; + } + + /* this is the client */ + + if (f < 0 && !am_sender) /* e.g. when we got an empty file list. */ + ; + else if (!am_sender) { + /* Read the first two in opposite order because the meaning of + * read/write swaps when switching from sender to receiver. */ + total_written = read_varlong30(f, 3); + total_read = read_varlong30(f, 3); + stats.total_size = read_varlong30(f, 3); + if (protocol_version >= 29) { + stats.flist_buildtime = read_varlong30(f, 3); + stats.flist_xfertime = read_varlong30(f, 3); + } + } else if (write_batch) { + /* The --read-batch process is going to be a client + * receiver, so we need to give it the stats. */ + write_varlong30(batch_fd, total_read, 3); + write_varlong30(batch_fd, total_written, 3); + write_varlong30(batch_fd, stats.total_size, 3); + if (protocol_version >= 29) { + write_varlong30(batch_fd, stats.flist_buildtime, 3); + write_varlong30(batch_fd, stats.flist_xfertime, 3); + } + } +} + +static void output_itemized_counts(const char *prefix, int *counts) +{ + static char *labels[] = { "reg", "dir", "link", "dev", "special" }; + char buf[1024], *pre = " ("; + int j, len = 0; + int total = counts[0]; + if (total) { + counts[0] -= counts[1] + counts[2] + counts[3] + counts[4]; + for (j = 0; j < 5; j++) { + if (counts[j]) { + len += snprintf(buf+len, sizeof buf - len - 2, + "%s%s: %s", + pre, labels[j], comma_num(counts[j])); + pre = ", "; + } + } + buf[len++] = ')'; + } + buf[len] = '\0'; + rprintf(FINFO, "%s: %s%s\n", prefix, comma_num(total), buf); +} + +static void output_summary(void) +{ + if (INFO_GTE(STATS, 2)) { + rprintf(FCLIENT, "\n"); + output_itemized_counts("Number of files", &stats.num_files); + if (protocol_version >= 29) + output_itemized_counts("Number of created files", &stats.created_files); + if (protocol_version >= 31) + output_itemized_counts("Number of deleted files", &stats.deleted_files); + rprintf(FINFO,"Number of regular files transferred: %s\n", + comma_num(stats.xferred_files)); + rprintf(FINFO,"Total file size: %s bytes\n", + human_num(stats.total_size)); + rprintf(FINFO,"Total transferred file size: %s bytes\n", + human_num(stats.total_transferred_size)); + rprintf(FINFO,"Literal data: %s bytes\n", + human_num(stats.literal_data)); + rprintf(FINFO,"Matched data: %s bytes\n", + human_num(stats.matched_data)); + rprintf(FINFO,"File list size: %s\n", + human_num(stats.flist_size)); + if (stats.flist_buildtime) { + rprintf(FINFO, + "File list generation time: %s seconds\n", + comma_dnum((double)stats.flist_buildtime / 1000, 3)); + rprintf(FINFO, + "File list transfer time: %s seconds\n", + comma_dnum((double)stats.flist_xfertime / 1000, 3)); + } + rprintf(FINFO,"Total bytes sent: %s\n", + human_num(total_written)); + rprintf(FINFO,"Total bytes received: %s\n", + human_num(total_read)); + } + + if (INFO_GTE(STATS, 1)) { + rprintf(FCLIENT, "\n"); + rprintf(FINFO, + "sent %s bytes received %s bytes %s bytes/sec\n", + human_num(total_written), human_num(total_read), + human_dnum((total_written + total_read)/(0.5 + (endtime - starttime)), 2)); + rprintf(FINFO, "total size is %s speedup is %s%s\n", + human_num(stats.total_size), + comma_dnum((double)stats.total_size / (total_written+total_read), 2), + write_batch < 0 ? " (BATCH ONLY)" : dry_run ? " (DRY RUN)" : ""); + } + + fflush(stdout); + fflush(stderr); +} + + +/** + * If our C library can get malloc statistics, then show them to FINFO + **/ +static void show_malloc_stats(void) +{ +#ifdef HAVE_MALLINFO + struct mallinfo mi; + + mi = mallinfo(); + + rprintf(FCLIENT, "\n"); + rprintf(FINFO, RSYNC_NAME "[%d] (%s%s%s) heap statistics:\n", + (int)getpid(), am_server ? "server " : "", + am_daemon ? "daemon " : "", who_am_i()); + rprintf(FINFO, " arena: %10ld (bytes from sbrk)\n", + (long)mi.arena); + rprintf(FINFO, " ordblks: %10ld (chunks not in use)\n", + (long)mi.ordblks); + rprintf(FINFO, " smblks: %10ld\n", + (long)mi.smblks); + rprintf(FINFO, " hblks: %10ld (chunks from mmap)\n", + (long)mi.hblks); + rprintf(FINFO, " hblkhd: %10ld (bytes from mmap)\n", + (long)mi.hblkhd); + rprintf(FINFO, " allmem: %10ld (bytes from sbrk + mmap)\n", + (long)mi.arena + mi.hblkhd); + rprintf(FINFO, " usmblks: %10ld\n", + (long)mi.usmblks); + rprintf(FINFO, " fsmblks: %10ld\n", + (long)mi.fsmblks); + rprintf(FINFO, " uordblks: %10ld (bytes used)\n", + (long)mi.uordblks); + rprintf(FINFO, " fordblks: %10ld (bytes free)\n", + (long)mi.fordblks); + rprintf(FINFO, " keepcost: %10ld (bytes in releasable chunk)\n", + (long)mi.keepcost); +#endif /* HAVE_MALLINFO */ +} + + +/* Start the remote shell. cmd may be NULL to use the default. */ +static pid_t do_cmd(char *cmd, char *machine, char *user, char **remote_argv, int remote_argc, + int *f_in_p, int *f_out_p) +{ + int i, argc = 0; + char *args[MAX_ARGS], *need_to_free = NULL; + pid_t pid; + int dash_l_set = 0; + + if (!read_batch && !local_server) { + char *t, *f, in_quote = '\0'; + char *rsh_env = getenv(RSYNC_RSH_ENV); + if (!cmd) + cmd = rsh_env; + if (!cmd) + cmd = RSYNC_RSH; + cmd = need_to_free = strdup(cmd); + if (!cmd) + goto oom; + + for (t = f = cmd; *f; f++) { + if (*f == ' ') + continue; + /* Comparison leaves rooms for server_options(). */ + if (argc >= MAX_ARGS - MAX_SERVER_ARGS) + goto arg_overflow; + args[argc++] = t; + while (*f != ' ' || in_quote) { + if (!*f) { + if (in_quote) { + rprintf(FERROR, + "Missing trailing-%c in remote-shell command.\n", + in_quote); + exit_cleanup(RERR_SYNTAX); + } + f--; + break; + } + if (*f == '\'' || *f == '"') { + if (!in_quote) { + in_quote = *f++; + continue; + } + if (*f == in_quote && *++f != in_quote) { + in_quote = '\0'; + continue; + } + } + *t++ = *f++; + } + *t++ = '\0'; + } + + /* check to see if we've already been given '-l user' in + * the remote-shell command */ + for (i = 0; i < argc-1; i++) { + if (!strcmp(args[i], "-l") && args[i+1][0] != '-') + dash_l_set = 1; + } + +#ifdef HAVE_REMSH + /* remsh (on HPUX) takes the arguments the other way around */ + args[argc++] = machine; + if (user && !(daemon_over_rsh && dash_l_set)) { + args[argc++] = "-l"; + args[argc++] = user; + } +#else + if (user && !(daemon_over_rsh && dash_l_set)) { + args[argc++] = "-l"; + args[argc++] = user; + } + args[argc++] = machine; +#endif + + args[argc++] = rsync_path; + + if (blocking_io < 0) { + char *cp; + if ((cp = strrchr(cmd, '/')) != NULL) + cp++; + else + cp = cmd; + if (strcmp(cp, "rsh") == 0 || strcmp(cp, "remsh") == 0) + blocking_io = 1; + } + + server_options(args,&argc); + + if (argc >= MAX_ARGS - 2) + goto arg_overflow; + } + + args[argc++] = "."; + + if (!daemon_over_rsh) { + while (remote_argc > 0) { + if (argc >= MAX_ARGS - 1) { + arg_overflow: + rprintf(FERROR, "internal: args[] overflowed in do_cmd()\n"); + exit_cleanup(RERR_SYNTAX); + } + if (**remote_argv == '-') { + if (asprintf(args + argc++, "./%s", *remote_argv++) < 0) + out_of_memory("do_cmd"); + } else + args[argc++] = *remote_argv++; + remote_argc--; + } + } + + args[argc] = NULL; + + if (DEBUG_GTE(CMD, 2)) { + for (i = 0; i < argc; i++) + rprintf(FCLIENT, "cmd[%d]=%s ", i, args[i]); + rprintf(FCLIENT, "\n"); + } + + if (read_batch) { + int from_gen_pipe[2]; + set_allow_inc_recurse(); + if (fd_pair(from_gen_pipe) < 0) { + rsyserr(FERROR, errno, "pipe"); + exit_cleanup(RERR_IPC); + } + batch_gen_fd = from_gen_pipe[0]; + *f_out_p = from_gen_pipe[1]; + *f_in_p = batch_fd; + pid = (pid_t)-1; /* no child pid */ +#ifdef ICONV_CONST + setup_iconv(); +#endif + } else if (local_server) { + /* If the user didn't request --[no-]whole-file, force + * it on, but only if we're not batch processing. */ + if (whole_file < 0 && !write_batch) + whole_file = 1; + set_allow_inc_recurse(); + pid = local_child(argc, args, f_in_p, f_out_p, child_main); +#ifdef ICONV_CONST + setup_iconv(); +#endif + } else { + pid = piped_child(args, f_in_p, f_out_p); +#ifdef ICONV_CONST + setup_iconv(); +#endif + if (protect_args && !daemon_over_rsh) + send_protected_args(*f_out_p, args); + } + + if (need_to_free) + free(need_to_free); + + return pid; + + oom: + out_of_memory("do_cmd"); + return 0; /* not reached */ +} + +/* The receiving side operates in one of two modes: + * + * 1. it receives any number of files into a destination directory, + * placing them according to their names in the file-list. + * + * 2. it receives a single file and saves it using the name in the + * destination path instead of its file-list name. This requires a + * "local name" for writing out the destination file. + * + * So, our task is to figure out what mode/local-name we need. + * For mode 1, we change into the destination directory and return NULL. + * For mode 2, we change into the directory containing the destination + * file (if we aren't already there) and return the local-name. */ +static char *get_local_name(struct file_list *flist, char *dest_path) +{ + STRUCT_STAT st; + int statret; + char *cp; + + if (DEBUG_GTE(RECV, 1)) { + rprintf(FINFO, "get_local_name count=%d %s\n", + file_total, NS(dest_path)); + } + + if (!dest_path || list_only) + return NULL; + + /* Treat an empty string as a copy into the current directory. */ + if (!*dest_path) + dest_path = "."; + + if (daemon_filter_list.head) { + char *slash = strrchr(dest_path, '/'); + if (slash && (slash[1] == '\0' || (slash[1] == '.' && slash[2] == '\0'))) + *slash = '\0'; + else + slash = NULL; + if ((*dest_path != '.' || dest_path[1] != '\0') + && (check_filter(&daemon_filter_list, FLOG, dest_path, 0) < 0 + || check_filter(&daemon_filter_list, FLOG, dest_path, 1) < 0)) { + rprintf(FERROR, "ERROR: daemon has excluded destination \"%s\"\n", + dest_path); + exit_cleanup(RERR_FILESELECT); + } + if (slash) + *slash = '/'; + } + + /* See what currently exists at the destination. */ + if ((statret = do_stat(dest_path, &st)) == 0) { + /* If the destination is a dir, enter it and use mode 1. */ + if (S_ISDIR(st.st_mode)) { + if (!change_dir(dest_path, CD_NORMAL)) { + rsyserr(FERROR, errno, "change_dir#1 %s failed", + full_fname(dest_path)); + exit_cleanup(RERR_FILESELECT); + } + filesystem_dev = st.st_dev; /* ensures --force works right w/-x */ + return NULL; + } + if (file_total > 1) { + rprintf(FERROR, + "ERROR: destination must be a directory when" + " copying more than 1 file\n"); + exit_cleanup(RERR_FILESELECT); + } + if (file_total == 1 && S_ISDIR(flist->files[0]->mode)) { + rprintf(FERROR, + "ERROR: cannot overwrite non-directory" + " with a directory\n"); + exit_cleanup(RERR_FILESELECT); + } + } else if (errno != ENOENT) { + /* If we don't know what's at the destination, fail. */ + rsyserr(FERROR, errno, "ERROR: cannot stat destination %s", + full_fname(dest_path)); + exit_cleanup(RERR_FILESELECT); + } + + cp = strrchr(dest_path, '/'); + + /* If we need a destination directory because the transfer is not + * of a single non-directory or the user has requested one via a + * destination path ending in a slash, create one and use mode 1. */ + if (file_total > 1 || (cp && !cp[1])) { + /* Lop off the final slash (if any). */ + if (cp && !cp[1]) + *cp = '\0'; + + if (statret == 0) { + rprintf(FERROR, + "ERROR: destination path is not a directory\n"); + exit_cleanup(RERR_SYNTAX); + } + + if (do_mkdir(dest_path, ACCESSPERMS) != 0) { + rsyserr(FERROR, errno, "mkdir %s failed", + full_fname(dest_path)); + exit_cleanup(RERR_FILEIO); + } + + if (flist->high >= flist->low + && strcmp(flist->files[flist->low]->basename, ".") == 0) + flist->files[0]->flags |= FLAG_DIR_CREATED; + + if (INFO_GTE(NAME, 1)) + rprintf(FINFO, "created directory %s\n", dest_path); + + if (dry_run) { + /* Indicate that dest dir doesn't really exist. */ + dry_run++; + } + + if (!change_dir(dest_path, dry_run > 1 ? CD_SKIP_CHDIR : CD_NORMAL)) { + rsyserr(FERROR, errno, "change_dir#2 %s failed", + full_fname(dest_path)); + exit_cleanup(RERR_FILESELECT); + } + + return NULL; + } + + /* Otherwise, we are writing a single file, possibly on top of an + * existing non-directory. Change to the item's parent directory + * (if it has a path component), return the basename of the + * destination file as the local name, and use mode 2. */ + if (!cp) + return dest_path; + + if (cp == dest_path) + dest_path = "/"; + + *cp = '\0'; + if (!change_dir(dest_path, CD_NORMAL)) { + rsyserr(FERROR, errno, "change_dir#3 %s failed", + full_fname(dest_path)); + exit_cleanup(RERR_FILESELECT); + } + *cp = '/'; + + return cp + 1; +} + +/* This function checks on our alternate-basis directories. If we're in + * dry-run mode and the destination dir does not yet exist, we'll try to + * tweak any dest-relative paths to make them work for a dry-run (the + * destination dir must be in curr_dir[] when this function is called). + * We also warn about any arg that is non-existent or not a directory. */ +static void check_alt_basis_dirs(void) +{ + STRUCT_STAT st; + char *slash = strrchr(curr_dir, '/'); + int j; + + for (j = 0; j < basis_dir_cnt; j++) { + char *bdir = basis_dir[j]; + int bd_len = strlen(bdir); + if (bd_len > 1 && bdir[bd_len-1] == '/') + bdir[--bd_len] = '\0'; + if (dry_run > 1 && *bdir != '/') { + int len = curr_dir_len + 1 + bd_len + 1; + char *new = new_array(char, len); + if (!new) + out_of_memory("check_alt_basis_dirs"); + if (slash && strncmp(bdir, "../", 3) == 0) { + /* We want to remove only one leading "../" prefix for + * the directory we couldn't create in dry-run mode: + * this ensures that any other ".." references get + * evaluated the same as they would for a live copy. */ + *slash = '\0'; + pathjoin(new, len, curr_dir, bdir + 3); + *slash = '/'; + } else + pathjoin(new, len, curr_dir, bdir); + basis_dir[j] = bdir = new; + } + if (do_stat(bdir, &st) < 0) + rprintf(FWARNING, "%s arg does not exist: %s\n", dest_option, bdir); + else if (!S_ISDIR(st.st_mode)) + rprintf(FWARNING, "%s arg is not a dir: %s\n", dest_option, bdir); + } +} + +/* This is only called by the sender. */ +static void read_final_goodbye(int f_in, int f_out) +{ + int i, iflags, xlen; + uchar fnamecmp_type; + char xname[MAXPATHLEN]; + + shutting_down = True; + + if (protocol_version < 29) + i = read_int(f_in); + else { + i = read_ndx_and_attrs(f_in, f_out, &iflags, &fnamecmp_type, xname, &xlen); + if (protocol_version >= 31 && i == NDX_DONE) { + if (am_sender) + write_ndx(f_out, NDX_DONE); + else { + if (batch_gen_fd >= 0) { + while (read_int(batch_gen_fd) != NDX_DEL_STATS) {} + read_del_stats(batch_gen_fd); + } + write_int(f_out, NDX_DONE); + } + i = read_ndx_and_attrs(f_in, f_out, &iflags, &fnamecmp_type, xname, &xlen); + } + } + + if (i != NDX_DONE) { + rprintf(FERROR, "Invalid packet at end of run (%d) [%s]\n", + i, who_am_i()); + exit_cleanup(RERR_PROTOCOL); + } +} + +static void do_server_sender(int f_in, int f_out, int argc, char *argv[]) +{ + struct file_list *flist; + char *dir = argv[0]; + + if (DEBUG_GTE(SEND, 1)) + rprintf(FINFO, "server_sender starting pid=%d\n", (int)getpid()); + + if (am_daemon && lp_write_only(module_id)) { + rprintf(FERROR, "ERROR: module is write only\n"); + exit_cleanup(RERR_SYNTAX); + return; + } + if (am_daemon && read_only && remove_source_files) { + rprintf(FERROR, + "ERROR: --remove-%s-files cannot be used with a read-only module\n", + remove_source_files == 1 ? "source" : "sent"); + exit_cleanup(RERR_SYNTAX); + return; + } + + if (!relative_paths) { + if (!change_dir(dir, CD_NORMAL)) { + rsyserr(FERROR, errno, "change_dir#3 %s failed", + full_fname(dir)); + exit_cleanup(RERR_FILESELECT); + } + } + argc--; + argv++; + + if (argc == 0 && (recurse || xfer_dirs || list_only)) { + argc = 1; + argv--; + argv[0] = "."; + } + + flist = send_file_list(f_out,argc,argv); + if (!flist || flist->used == 0) { + /* Make sure input buffering is off so we can't hang in noop_io_until_death(). */ + io_end_buffering_in(0); + /* TODO: we should really exit in a more controlled manner. */ + exit_cleanup(0); + } + + io_start_buffering_in(f_in); + + send_files(f_in, f_out); + io_flush(FULL_FLUSH); + handle_stats(f_out); + if (protocol_version >= 24) + read_final_goodbye(f_in, f_out); + io_flush(FULL_FLUSH); + exit_cleanup(0); +} + + +static int do_recv(int f_in, int f_out, char *local_name) +{ + int pid; + int exit_code = 0; + int error_pipe[2]; + + /* The receiving side mustn't obey this, or an existing symlink that + * points to an identical file won't be replaced by the referent. */ + copy_links = copy_dirlinks = copy_unsafe_links = 0; + +#ifdef SUPPORT_HARD_LINKS + if (preserve_hard_links && !inc_recurse) + match_hard_links(first_flist); +#endif + + if (fd_pair(error_pipe) < 0) { + rsyserr(FERROR, errno, "pipe failed in do_recv"); + exit_cleanup(RERR_IPC); + } + + if (backup_dir) { + int ret = make_path(backup_dir_buf, MKP_DROP_NAME); /* drops trailing slash */ + if (ret < 0) + exit_cleanup(RERR_SYNTAX); + if (ret) + rprintf(FINFO, "Created backup_dir %s\n", backup_dir_buf); + else if (INFO_GTE(BACKUP, 1)) + rprintf(FINFO, "backup_dir is %s\n", backup_dir_buf); + } + + io_flush(FULL_FLUSH); + + if ((pid = do_fork()) == -1) { + rsyserr(FERROR, errno, "fork failed in do_recv"); + exit_cleanup(RERR_IPC); + } + + if (pid == 0) { + am_receiver = 1; + send_msgs_to_gen = am_server; + + close(error_pipe[0]); + + /* We can't let two processes write to the socket at one time. */ + io_end_multiplex_out(MPLX_SWITCHING); + if (f_in != f_out) + close(f_out); + sock_f_out = -1; + f_out = error_pipe[1]; + + bwlimit_writemax = 0; /* receiver doesn't need to do this */ + + if (read_batch) + io_start_buffering_in(f_in); + io_start_multiplex_out(f_out); + + recv_files(f_in, f_out, local_name); + io_flush(FULL_FLUSH); + handle_stats(f_in); + + if (output_needs_newline) { + fputc('\n', stdout); + output_needs_newline = 0; + } + + write_int(f_out, NDX_DONE); + send_msg(MSG_STATS, (char*)&stats.total_read, sizeof stats.total_read, 0); + io_flush(FULL_FLUSH); + + /* Handle any keep-alive packets from the post-processing work + * that the generator does. */ + if (protocol_version >= 29) { + kluge_around_eof = -1; + + /* This should only get stopped via a USR2 signal. */ + read_final_goodbye(f_in, f_out); + + rprintf(FERROR, "Invalid packet at end of run [%s]\n", + who_am_i()); + exit_cleanup(RERR_PROTOCOL); + } + + /* Finally, we go to sleep until our parent kills us with a + * USR2 signal. We sleep for a short time, as on some OSes + * a signal won't interrupt a sleep! */ + while (1) + msleep(20); + } + + am_generator = 1; + flist_receiving_enabled = True; + + io_end_multiplex_in(MPLX_SWITCHING); + if (write_batch && !am_server) + stop_write_batch(); + + close(error_pipe[1]); + if (f_in != f_out) + close(f_in); + sock_f_in = -1; + f_in = error_pipe[0]; + + io_start_buffering_out(f_out); + io_start_multiplex_in(f_in); + +#ifdef SUPPORT_HARD_LINKS + if (preserve_hard_links && inc_recurse) { + struct file_list *flist; + for (flist = first_flist; flist; flist = flist->next) + match_hard_links(flist); + } +#endif + + generate_files(f_out, local_name); + + handle_stats(-1); + io_flush(FULL_FLUSH); + shutting_down = True; + if (protocol_version >= 24) { + /* send a final goodbye message */ + write_ndx(f_out, NDX_DONE); + } + io_flush(FULL_FLUSH); + + kill(pid, SIGUSR2); + wait_process_with_flush(pid, &exit_code); + return exit_code; +} + +static void do_server_recv(int f_in, int f_out, int argc, char *argv[]) +{ + int exit_code; + struct file_list *flist; + char *local_name = NULL; + int negated_levels; + + if (filesfrom_fd >= 0 && !msgs2stderr && protocol_version < 31) { + /* We can't mix messages with files-from data on the socket, + * so temporarily turn off info/debug messages. */ + negate_output_levels(); + negated_levels = 1; + } else + negated_levels = 0; + + if (DEBUG_GTE(RECV, 1)) + rprintf(FINFO, "server_recv(%d) starting pid=%d\n", argc, (int)getpid()); + + if (am_daemon && read_only) { + rprintf(FERROR,"ERROR: module is read only\n"); + exit_cleanup(RERR_SYNTAX); + return; + } + + if (argc > 0) { + char *dir = argv[0]; + argc--; + argv++; + if (!am_daemon && !change_dir(dir, CD_NORMAL)) { + rsyserr(FERROR, errno, "change_dir#4 %s failed", + full_fname(dir)); + exit_cleanup(RERR_FILESELECT); + } + } + + if (protocol_version >= 30) + io_start_multiplex_in(f_in); + else + io_start_buffering_in(f_in); + recv_filter_list(f_in); + + if (filesfrom_fd >= 0) { + /* We need to send the files-from names to the sender at the + * same time that we receive the file-list from them, so we + * need the IO routines to automatically write out the names + * onto our f_out socket as we read the file-list. This + * avoids both deadlock and extra delays/buffers. */ + start_filesfrom_forwarding(filesfrom_fd); + filesfrom_fd = -1; + } + + flist = recv_file_list(f_in); + if (!flist) { + rprintf(FERROR,"server_recv: recv_file_list error\n"); + exit_cleanup(RERR_FILESELECT); + } + if (inc_recurse && file_total == 1) + recv_additional_file_list(f_in); + + if (negated_levels) + negate_output_levels(); + + if (argc > 0) + local_name = get_local_name(flist,argv[0]); + + /* Now that we know what our destination directory turned out to be, + * we can sanitize the --link-/copy-/compare-dest args correctly. */ + if (sanitize_paths) { + char **dir_p; + for (dir_p = basis_dir; *dir_p; dir_p++) + *dir_p = sanitize_path(NULL, *dir_p, NULL, curr_dir_depth, SP_DEFAULT); + if (partial_dir) + partial_dir = sanitize_path(NULL, partial_dir, NULL, curr_dir_depth, SP_DEFAULT); + } + check_alt_basis_dirs(); + + if (daemon_filter_list.head) { + char **dir_p; + filter_rule_list *elp = &daemon_filter_list; + + for (dir_p = basis_dir; *dir_p; dir_p++) { + char *dir = *dir_p; + if (*dir == '/') + dir += module_dirlen; + if (check_filter(elp, FLOG, dir, 1) < 0) + goto options_rejected; + } + if (partial_dir && *partial_dir == '/' + && check_filter(elp, FLOG, partial_dir + module_dirlen, 1) < 0) { + options_rejected: + rprintf(FERROR, + "Your options have been rejected by the server.\n"); + exit_cleanup(RERR_SYNTAX); + } + } + + exit_code = do_recv(f_in, f_out, local_name); + exit_cleanup(exit_code); +} + + +int child_main(int argc, char *argv[]) +{ + start_server(STDIN_FILENO, STDOUT_FILENO, argc, argv); + return 0; +} + + +void start_server(int f_in, int f_out, int argc, char *argv[]) +{ + set_nonblocking(f_in); + set_nonblocking(f_out); + + io_set_sock_fds(f_in, f_out); + setup_protocol(f_out, f_in); + + if (protocol_version >= 23) + io_start_multiplex_out(f_out); + if (am_daemon && io_timeout && protocol_version >= 31) + send_msg_int(MSG_IO_TIMEOUT, io_timeout); + + if (am_sender) { + keep_dirlinks = 0; /* Must be disabled on the sender. */ + if (need_messages_from_generator) + io_start_multiplex_in(f_in); + else + io_start_buffering_in(f_in); + recv_filter_list(f_in); + do_server_sender(f_in, f_out, argc, argv); + } else + do_server_recv(f_in, f_out, argc, argv); + exit_cleanup(0); +} + +/* This is called once the connection has been negotiated. It is used + * for rsyncd, remote-shell, and local connections. */ +int client_run(int f_in, int f_out, pid_t pid, int argc, char *argv[]) +{ + struct file_list *flist = NULL; + int exit_code = 0, exit_code2 = 0; + char *local_name = NULL; + + cleanup_child_pid = pid; + if (!read_batch) { + set_nonblocking(f_in); + set_nonblocking(f_out); + } + + io_set_sock_fds(f_in, f_out); + setup_protocol(f_out,f_in); + + /* We set our stderr file handle to blocking because ssh might have + * set it to non-blocking. This can be particularly troublesome if + * stderr is a clone of stdout, because ssh would have set our stdout + * to non-blocking at the same time (which can easily cause us to lose + * output from our print statements). This kluge shouldn't cause ssh + * any problems for how we use it. Note also that we delayed setting + * this until after the above protocol setup so that we know for sure + * that ssh is done twiddling its file descriptors. */ + set_blocking(STDERR_FILENO); + + if (am_sender) { + keep_dirlinks = 0; /* Must be disabled on the sender. */ + + if (always_checksum + && (log_format_has(stdout_format, 'C') + || log_format_has(logfile_format, 'C'))) + sender_keeps_checksum = 1; + + if (protocol_version >= 30) + io_start_multiplex_out(f_out); + else + io_start_buffering_out(f_out); + if (protocol_version >= 31 || (!filesfrom_host && protocol_version >= 23)) + io_start_multiplex_in(f_in); + else + io_start_buffering_in(f_in); + send_filter_list(f_out); + if (filesfrom_host) + filesfrom_fd = f_in; + + if (write_batch && !am_server) + start_write_batch(f_out); + flist = send_file_list(f_out, argc, argv); + if (DEBUG_GTE(FLIST, 3)) + rprintf(FINFO,"file list sent\n"); + + if (protocol_version < 31 && filesfrom_host && protocol_version >= 23) + io_start_multiplex_in(f_in); + + io_flush(NORMAL_FLUSH); + send_files(f_in, f_out); + io_flush(FULL_FLUSH); + handle_stats(-1); + if (protocol_version >= 24) + read_final_goodbye(f_in, f_out); + if (pid != -1) { + if (DEBUG_GTE(EXIT, 2)) + rprintf(FINFO,"client_run waiting on %d\n", (int) pid); + io_flush(FULL_FLUSH); + wait_process_with_flush(pid, &exit_code); + } + output_summary(); + io_flush(FULL_FLUSH); + exit_cleanup(exit_code); + } + + if (!read_batch) { + if (protocol_version >= 23) + io_start_multiplex_in(f_in); + if (need_messages_from_generator) + io_start_multiplex_out(f_out); + else + io_start_buffering_out(f_out); + } + + send_filter_list(read_batch ? -1 : f_out); + + if (filesfrom_fd >= 0) { + start_filesfrom_forwarding(filesfrom_fd); + filesfrom_fd = -1; + } + + if (write_batch && !am_server) + start_write_batch(f_in); + flist = recv_file_list(f_in); + if (inc_recurse && file_total == 1) + recv_additional_file_list(f_in); + + if (flist && flist->used > 0) { + local_name = get_local_name(flist, argv[0]); + + check_alt_basis_dirs(); + + exit_code2 = do_recv(f_in, f_out, local_name); + } else { + handle_stats(-1); + output_summary(); + } + + if (pid != -1) { + if (DEBUG_GTE(RECV, 1)) + rprintf(FINFO,"client_run2 waiting on %d\n", (int) pid); + io_flush(FULL_FLUSH); + wait_process_with_flush(pid, &exit_code); + } + + return MAX(exit_code, exit_code2); +} + +static int copy_argv(char *argv[]) +{ + int i; + + for (i = 0; argv[i]; i++) { + if (!(argv[i] = strdup(argv[i]))) { + rprintf (FERROR, "out of memory at %s(%d)\n", + __FILE__, __LINE__); + return RERR_MALLOC; + } + } + + return 0; +} + + +/* Start a client for either type of remote connection. Work out + * whether the arguments request a remote shell or rsyncd connection, + * and call the appropriate connection function, then run_client. + * + * Calls either start_socket_client (for sockets) or do_cmd and + * client_run (for ssh). */ +static int start_client(int argc, char *argv[]) +{ + char *p, *shell_machine = NULL, *shell_user = NULL; + char **remote_argv; + int remote_argc; + int f_in, f_out; + int ret; + pid_t pid; + + /* Don't clobber argv[] so that ps(1) can still show the right + * command line. */ + if ((ret = copy_argv(argv)) != 0) + return ret; + + if (!read_batch) { /* for read_batch, NO source is specified */ + char *path = check_for_hostspec(argv[0], &shell_machine, &rsync_port); + if (path) { /* source is remote */ + char *dummy_host; + int dummy_port = 0; + *argv = path; + remote_argv = argv; + remote_argc = argc; + argv += argc - 1; + if (argc == 1 || **argv == ':') + argc = 0; /* no dest arg */ + else if (check_for_hostspec(*argv, &dummy_host, &dummy_port)) { + rprintf(FERROR, + "The source and destination cannot both be remote.\n"); + exit_cleanup(RERR_SYNTAX); + } else { + remote_argc--; /* don't count dest */ + argc = 1; + } + if (filesfrom_host && *filesfrom_host + && strcmp(filesfrom_host, shell_machine) != 0) { + rprintf(FERROR, + "--files-from hostname is not the same as the transfer hostname\n"); + exit_cleanup(RERR_SYNTAX); + } + am_sender = 0; + if (rsync_port) + daemon_over_rsh = shell_cmd ? 1 : -1; + } else { /* source is local, check dest arg */ + am_sender = 1; + + if (argc > 1) { + p = argv[--argc]; + remote_argv = argv + argc; + } else { + static char *dotarg[1] = { "." }; + p = dotarg[0]; + remote_argv = dotarg; + } + remote_argc = 1; + + path = check_for_hostspec(p, &shell_machine, &rsync_port); + if (path && filesfrom_host && *filesfrom_host + && strcmp(filesfrom_host, shell_machine) != 0) { + rprintf(FERROR, + "--files-from hostname is not the same as the transfer hostname\n"); + exit_cleanup(RERR_SYNTAX); + } + if (!path) { /* no hostspec found, so src & dest are local */ + local_server = 1; + if (filesfrom_host) { + rprintf(FERROR, + "--files-from cannot be remote when the transfer is local\n"); + exit_cleanup(RERR_SYNTAX); + } + shell_machine = NULL; + } else { /* hostspec was found, so dest is remote */ + argv[argc] = path; + if (rsync_port) + daemon_over_rsh = shell_cmd ? 1 : -1; + } + } + } else { /* read_batch */ + local_server = 1; + if (check_for_hostspec(argv[argc-1], &shell_machine, &rsync_port)) { + rprintf(FERROR, "remote destination is not allowed with --read-batch\n"); + exit_cleanup(RERR_SYNTAX); + } + remote_argv = argv += argc - 1; + remote_argc = argc = 1; + } + + if (!rsync_port && remote_argc && !**remote_argv) /* Turn an empty arg into a dot dir. */ + *remote_argv = "."; + + if (am_sender) { + char *dummy_host; + int dummy_port = rsync_port; + int i; + /* For local source, extra source args must not have hostspec. */ + for (i = 1; i < argc; i++) { + if (check_for_hostspec(argv[i], &dummy_host, &dummy_port)) { + rprintf(FERROR, "Unexpected remote arg: %s\n", argv[i]); + exit_cleanup(RERR_SYNTAX); + } + } + } else { + char *dummy_host; + int dummy_port = rsync_port; + int i; + /* For remote source, any extra source args must have either + * the same hostname or an empty hostname. */ + for (i = 1; i < remote_argc; i++) { + char *arg = check_for_hostspec(remote_argv[i], &dummy_host, &dummy_port); + if (!arg) { + rprintf(FERROR, "Unexpected local arg: %s\n", remote_argv[i]); + rprintf(FERROR, "If arg is a remote file/dir, prefix it with a colon (:).\n"); + exit_cleanup(RERR_SYNTAX); + } + if (*dummy_host && strcmp(dummy_host, shell_machine) != 0) { + rprintf(FERROR, "All source args must come from the same machine.\n"); + exit_cleanup(RERR_SYNTAX); + } + if (rsync_port != dummy_port) { + if (!rsync_port || !dummy_port) + rprintf(FERROR, "All source args must use the same hostspec format.\n"); + else + rprintf(FERROR, "All source args must use the same port number.\n"); + exit_cleanup(RERR_SYNTAX); + } + if (!rsync_port && !*arg) /* Turn an empty arg into a dot dir. */ + arg = "."; + remote_argv[i] = arg; + } + } + + if (daemon_over_rsh < 0) + return start_socket_client(shell_machine, remote_argc, remote_argv, argc, argv); + + if (password_file && !daemon_over_rsh) { + rprintf(FERROR, "The --password-file option may only be " + "used when accessing an rsync daemon.\n"); + exit_cleanup(RERR_SYNTAX); + } + + if (connect_timeout) { + rprintf(FERROR, "The --contimeout option may only be " + "used when connecting to an rsync daemon.\n"); + exit_cleanup(RERR_SYNTAX); + } + + if (shell_machine) { + p = strrchr(shell_machine,'@'); + if (p) { + *p = 0; + shell_user = shell_machine; + shell_machine = p+1; + } + } + + if (DEBUG_GTE(CMD, 2)) { + rprintf(FINFO,"cmd=%s machine=%s user=%s path=%s\n", + NS(shell_cmd), NS(shell_machine), NS(shell_user), + NS(remote_argv[0])); + } + + pid = do_cmd(shell_cmd, shell_machine, shell_user, remote_argv, remote_argc, + &f_in, &f_out); + + /* if we're running an rsync server on the remote host over a + * remote shell command, we need to do the RSYNCD protocol first */ + if (daemon_over_rsh) { + int tmpret; + tmpret = start_inband_exchange(f_in, f_out, shell_user, remote_argc, remote_argv); + if (tmpret < 0) + return tmpret; + } + + ret = client_run(f_in, f_out, pid, argc, argv); + + fflush(stdout); + fflush(stderr); + + return ret; +} + + +static RETSIGTYPE sigusr1_handler(UNUSED(int val)) +{ + exit_cleanup(RERR_SIGNAL1); +} + +static RETSIGTYPE sigusr2_handler(UNUSED(int val)) +{ + if (!am_server) + output_summary(); + close_all(); + if (got_xfer_error) + _exit(RERR_PARTIAL); + _exit(0); +} + +RETSIGTYPE remember_children(UNUSED(int val)) +{ +#ifdef WNOHANG + int cnt, status; + pid_t pid; + /* An empty waitpid() loop was put here by Tridge and we could never + * get him to explain why he put it in, so rather than taking it + * out we're instead saving the child exit statuses for later use. + * The waitpid() loop presumably eliminates all possibility of leaving + * zombie children, maybe that's why he did it. */ + while ((pid = waitpid(-1, &status, WNOHANG)) > 0) { + /* save the child's exit status */ + for (cnt = 0; cnt < MAXCHILDPROCS; cnt++) { + if (pid_stat_table[cnt].pid == 0) { + pid_stat_table[cnt].pid = pid; + pid_stat_table[cnt].status = status; + break; + } + } + } +#endif +#ifndef HAVE_SIGACTION + signal(SIGCHLD, remember_children); +#endif +} + + +/** + * This routine catches signals and tries to send them to gdb. + * + * Because it's called from inside a signal handler it ought not to + * use too many library routines. + * + * @todo Perhaps use "screen -X" instead/as well, to help people + * debugging without easy access to X. Perhaps use an environment + * variable, or just call a script? + * + * @todo The /proc/ magic probably only works on Linux (and + * Solaris?) Can we be more portable? + **/ +#ifdef MAINTAINER_MODE +const char *get_panic_action(void) +{ + const char *cmd_fmt = getenv("RSYNC_PANIC_ACTION"); + + if (cmd_fmt) + return cmd_fmt; + else + return "xterm -display :0 -T Panic -n Panic " + "-e gdb /proc/%d/exe %d"; +} + + +/** + * Handle a fatal signal by launching a debugger, controlled by $RSYNC_PANIC_ACTION. + * + * This signal handler is only installed if we were configured with + * --enable-maintainer-mode. Perhaps it should always be on and we + * should just look at the environment variable, but I'm a bit leery + * of a signal sending us into a busy loop. + **/ +static RETSIGTYPE rsync_panic_handler(UNUSED(int whatsig)) +{ + char cmd_buf[300]; + int ret, pid_int = getpid(); + + snprintf(cmd_buf, sizeof cmd_buf, get_panic_action(), pid_int, pid_int); + + /* Unless we failed to execute gdb, we allow the process to + * continue. I'm not sure if that's right. */ + ret = system(cmd_buf); + if (ret) + _exit(ret); +} +#endif + + +int main(int argc,char *argv[]) +{ + int ret; + int orig_argc = argc; + char **orig_argv = argv; +#ifdef HAVE_SIGACTION +# ifdef HAVE_SIGPROCMASK + sigset_t sigmask; + + sigemptyset(&sigmask); +# endif + sigact.sa_flags = SA_NOCLDSTOP; +#endif + SIGACTMASK(SIGUSR1, sigusr1_handler); + SIGACTMASK(SIGUSR2, sigusr2_handler); + SIGACTMASK(SIGCHLD, remember_children); +#ifdef MAINTAINER_MODE + SIGACTMASK(SIGSEGV, rsync_panic_handler); + SIGACTMASK(SIGFPE, rsync_panic_handler); + SIGACTMASK(SIGABRT, rsync_panic_handler); + SIGACTMASK(SIGBUS, rsync_panic_handler); +#endif + + starttime = time(NULL); + our_uid = MY_UID(); + our_gid = MY_GID(); + am_root = our_uid == 0; + + memset(&stats, 0, sizeof(stats)); + + if (argc < 2) { + usage(FERROR); + exit_cleanup(RERR_SYNTAX); + } + + /* Get the umask for use in permission calculations. We no longer set + * it to zero; that is ugly and pointless now that all the callers that + * relied on it have been reeducated to work with default ACLs. */ + umask(orig_umask = umask(0)); + +#if defined CONFIG_LOCALE && defined HAVE_SETLOCALE + setlocale(LC_CTYPE, ""); +#endif + + if (!parse_arguments(&argc, (const char ***) &argv)) { + /* FIXME: We ought to call the same error-handling + * code here, rather than relying on getopt. */ + option_error(); + exit_cleanup(RERR_SYNTAX); + } + + SIGACTMASK(SIGINT, sig_int); + SIGACTMASK(SIGHUP, sig_int); + SIGACTMASK(SIGTERM, sig_int); +#if defined HAVE_SIGACTION && HAVE_SIGPROCMASK + sigprocmask(SIG_UNBLOCK, &sigmask, NULL); +#endif + + /* Ignore SIGPIPE; we consistently check error codes and will + * see the EPIPE. */ + SIGACTION(SIGPIPE, SIG_IGN); +#ifdef SIGXFSZ + SIGACTION(SIGXFSZ, SIG_IGN); +#endif + + /* Initialize change_dir() here because on some old systems getcwd + * (implemented by forking "pwd" and reading its output) doesn't + * work when there are other child processes. Also, on all systems + * that implement getcwd that way "pwd" can't be found after chroot. */ + change_dir(NULL, CD_NORMAL); + + init_flist(); + + if ((write_batch || read_batch) && !am_server) { + if (write_batch) + write_batch_shell_file(orig_argc, orig_argv, argc); + + if (read_batch && strcmp(batch_name, "-") == 0) + batch_fd = STDIN_FILENO; + else { + batch_fd = do_open(batch_name, + write_batch ? O_WRONLY | O_CREAT | O_TRUNC + : O_RDONLY, S_IRUSR | S_IWUSR); + } + if (batch_fd < 0) { + rsyserr(FERROR, errno, "Batch file %s open error", + full_fname(batch_name)); + exit_cleanup(RERR_FILEIO); + } + if (read_batch) + read_stream_flags(batch_fd); + else + write_stream_flags(batch_fd); + } + if (write_batch < 0) + dry_run = 1; + + if (am_server) { +#ifdef ICONV_CONST + setup_iconv(); +#endif + } else if (am_daemon) + return daemon_main(); + + if (am_server && protect_args) { + char buf[MAXPATHLEN]; + protect_args = 2; + read_args(STDIN_FILENO, NULL, buf, sizeof buf, 1, &argv, &argc, NULL); + if (!parse_arguments(&argc, (const char ***) &argv)) { + option_error(); + exit_cleanup(RERR_SYNTAX); + } + } + + if (argc < 1) { + usage(FERROR); + exit_cleanup(RERR_SYNTAX); + } + + if (am_server) { + set_nonblocking(STDIN_FILENO); + set_nonblocking(STDOUT_FILENO); + if (am_daemon) + return start_daemon(STDIN_FILENO, STDOUT_FILENO); + start_server(STDIN_FILENO, STDOUT_FILENO, argc, argv); + } + + ret = start_client(argc, argv); + if (ret == -1) + exit_cleanup(RERR_STARTCLIENT); + else + exit_cleanup(ret); + + return ret; +} diff --git a/rsync/match.c b/rsync/match.c new file mode 100644 index 0000000..39d15ed --- /dev/null +++ b/rsync/match.c @@ -0,0 +1,448 @@ +/* + * Block matching used by the file-transfer code. + * + * Copyright (C) 1996 Andrew Tridgell + * Copyright (C) 1996 Paul Mackerras + * Copyright (C) 2003-2014 Wayne Davison + * + * 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, visit the http://fsf.org website. + */ + +#include "rsync.h" +#include "inums.h" + +extern int checksum_seed; +extern int append_mode; +extern int checksum_len; + +int updating_basis_file; +char sender_file_sum[MAX_DIGEST_LEN]; + +static int false_alarms; +static int hash_hits; +static int matches; +static int64 data_transfer; + +static int total_false_alarms; +static int total_hash_hits; +static int total_matches; + +extern struct stats stats; + +#define TRADITIONAL_TABLESIZE (1<<16) + +static uint32 tablesize; +static int32 *hash_table; + +#define SUM2HASH2(s1,s2) (((s1) + (s2)) & 0xFFFF) +#define SUM2HASH(sum) SUM2HASH2((sum)&0xFFFF,(sum)>>16) + +#define BIG_SUM2HASH(sum) ((sum)%tablesize) + +static void build_hash_table(struct sum_struct *s) +{ + static uint32 alloc_size; + int32 i; + + /* Dynamically calculate the hash table size so that the hash load + * for big files is about 80%. A number greater than the traditional + * size must be odd or s2 will not be able to span the entire set. */ + tablesize = (uint32)(s->count/8) * 10 + 11; + if (tablesize < TRADITIONAL_TABLESIZE) + tablesize = TRADITIONAL_TABLESIZE; + if (tablesize > alloc_size || tablesize < alloc_size - 16*1024) { + if (hash_table) + free(hash_table); + hash_table = new_array(int32, tablesize); + if (!hash_table) + out_of_memory("build_hash_table"); + alloc_size = tablesize; + } + + memset(hash_table, 0xFF, tablesize * sizeof hash_table[0]); + + if (tablesize == TRADITIONAL_TABLESIZE) { + for (i = 0; i < s->count; i++) { + uint32 t = SUM2HASH(s->sums[i].sum1); + s->sums[i].chain = hash_table[t]; + hash_table[t] = i; + } + } else { + for (i = 0; i < s->count; i++) { + uint32 t = BIG_SUM2HASH(s->sums[i].sum1); + s->sums[i].chain = hash_table[t]; + hash_table[t] = i; + } + } +} + + +static OFF_T last_match; + + +/* Transmit a literal and/or match token. + * + * This delightfully-named function is called either when we find a + * match and need to transmit all the unmatched data leading up to it, + * or when we get bored of accumulating literal data and just need to + * transmit it. As a result of this second case, it is called even if + * we have not matched at all! + * + * If i >= 0, the number of a matched token. If < 0, indicates we have + * only literal data. A -1 will send a 0-token-int too, and a -2 sends + * only literal data, w/o any token-int. */ +static void matched(int f, struct sum_struct *s, struct map_struct *buf, + OFF_T offset, int32 i) +{ + int32 n = (int32)(offset - last_match); /* max value: block_size (int32) */ + int32 j; + + if (DEBUG_GTE(DELTASUM, 2) && i >= 0) { + rprintf(FINFO, + "match at %s last_match=%s j=%d len=%ld n=%ld\n", + big_num(offset), big_num(last_match), i, + (long)s->sums[i].len, (long)n); + } + + send_token(f, i, buf, last_match, n, i < 0 ? 0 : s->sums[i].len); + data_transfer += n; + + if (i >= 0) { + stats.matched_data += s->sums[i].len; + n += s->sums[i].len; + } + + for (j = 0; j < n; j += CHUNK_SIZE) { + int32 n1 = MIN(CHUNK_SIZE, n - j); + sum_update(map_ptr(buf, last_match + j, n1), n1); + } + + if (i >= 0) + last_match = offset + s->sums[i].len; + else + last_match = offset; + + if (buf && INFO_GTE(PROGRESS, 1)) + show_progress(last_match, buf->file_size); +} + + +static void hash_search(int f,struct sum_struct *s, + struct map_struct *buf, OFF_T len) +{ + OFF_T offset, aligned_offset, end; + int32 k, want_i, aligned_i, backup; + char sum2[SUM_LENGTH]; + uint32 s1, s2, sum; + int more; + schar *map; + + /* want_i is used to encourage adjacent matches, allowing the RLL + * coding of the output to work more efficiently. */ + want_i = 0; + + if (DEBUG_GTE(DELTASUM, 2)) { + rprintf(FINFO, "hash search b=%ld len=%s\n", + (long)s->blength, big_num(len)); + } + + k = (int32)MIN(len, (OFF_T)s->blength); + + map = (schar *)map_ptr(buf, 0, k); + + sum = get_checksum1((char *)map, k); + s1 = sum & 0xFFFF; + s2 = sum >> 16; + if (DEBUG_GTE(DELTASUM, 3)) + rprintf(FINFO, "sum=%.8x k=%ld\n", sum, (long)k); + + offset = aligned_offset = aligned_i = 0; + + end = len + 1 - s->sums[s->count-1].len; + + if (DEBUG_GTE(DELTASUM, 3)) { + rprintf(FINFO, "hash search s->blength=%ld len=%s count=%s\n", + (long)s->blength, big_num(len), big_num(s->count)); + } + + do { + int done_csum2 = 0; + uint32 hash_entry; + int32 i, *prev; + + if (DEBUG_GTE(DELTASUM, 4)) { + rprintf(FINFO, "offset=%s sum=%04x%04x\n", + big_num(offset), s2 & 0xFFFF, s1 & 0xFFFF); + } + + if (tablesize == TRADITIONAL_TABLESIZE) { + hash_entry = SUM2HASH2(s1,s2); + if ((i = hash_table[hash_entry]) < 0) + goto null_hash; + sum = (s1 & 0xffff) | (s2 << 16); + } else { + sum = (s1 & 0xffff) | (s2 << 16); + hash_entry = BIG_SUM2HASH(sum); + if ((i = hash_table[hash_entry]) < 0) + goto null_hash; + } + prev = &hash_table[hash_entry]; + + hash_hits++; + do { + int32 l; + + /* When updating in-place, the chunk's offset must be + * either >= our offset or identical data at that offset. + * Remove any bypassed entries that we can never use. */ + if (updating_basis_file && s->sums[i].offset < offset + && !(s->sums[i].flags & SUMFLG_SAME_OFFSET)) { + *prev = s->sums[i].chain; + continue; + } + prev = &s->sums[i].chain; + + if (sum != s->sums[i].sum1) + continue; + + /* also make sure the two blocks are the same length */ + l = (int32)MIN((OFF_T)s->blength, len-offset); + if (l != s->sums[i].len) + continue; + + if (DEBUG_GTE(DELTASUM, 3)) { + rprintf(FINFO, + "potential match at %s i=%ld sum=%08x\n", + big_num(offset), (long)i, sum); + } + + if (!done_csum2) { + map = (schar *)map_ptr(buf,offset,l); + get_checksum2((char *)map,l,sum2); + done_csum2 = 1; + } + + if (memcmp(sum2,s->sums[i].sum2,s->s2length) != 0) { + false_alarms++; + continue; + } + + /* When updating in-place, the best possible match is + * one with an identical offset, so we prefer that over + * the adjacent want_i optimization. */ + if (updating_basis_file) { + /* All the generator's chunks start at blength boundaries. */ + while (aligned_offset < offset) { + aligned_offset += s->blength; + aligned_i++; + } + if ((offset == aligned_offset + || (sum == 0 && l == s->blength && aligned_offset + l <= len)) + && aligned_i < s->count) { + if (i != aligned_i) { + if (sum != s->sums[aligned_i].sum1 + || l != s->sums[aligned_i].len + || memcmp(sum2, s->sums[aligned_i].sum2, s->s2length) != 0) + goto check_want_i; + i = aligned_i; + } + if (offset != aligned_offset) { + /* We've matched some zeros in a spot that is also zeros + * further along in the basis file, if we find zeros ahead + * in the sender's file, we'll output enough literal data + * to re-align with the basis file, and get back to seeking + * instead of writing. */ + backup = (int32)(aligned_offset - last_match); + if (backup < 0) + backup = 0; + map = (schar *)map_ptr(buf, aligned_offset - backup, l + backup) + + backup; + sum = get_checksum1((char *)map, l); + if (sum != s->sums[i].sum1) + goto check_want_i; + get_checksum2((char *)map, l, sum2); + if (memcmp(sum2, s->sums[i].sum2, s->s2length) != 0) + goto check_want_i; + /* OK, we have a re-alignment match. Bump the offset + * forward to the new match point. */ + offset = aligned_offset; + } + /* This identical chunk is in the same spot in the old and new file. */ + s->sums[i].flags |= SUMFLG_SAME_OFFSET; + want_i = i; + } + } + + check_want_i: + /* we've found a match, but now check to see + * if want_i can hint at a better match. */ + if (i != want_i && want_i < s->count + && (!updating_basis_file || s->sums[want_i].offset >= offset + || s->sums[want_i].flags & SUMFLG_SAME_OFFSET) + && sum == s->sums[want_i].sum1 + && memcmp(sum2, s->sums[want_i].sum2, s->s2length) == 0) { + /* we've found an adjacent match - the RLL coder + * will be happy */ + i = want_i; + } + want_i = i + 1; + + matched(f,s,buf,offset,i); + offset += s->sums[i].len - 1; + k = (int32)MIN((OFF_T)s->blength, len-offset); + map = (schar *)map_ptr(buf, offset, k); + sum = get_checksum1((char *)map, k); + s1 = sum & 0xFFFF; + s2 = sum >> 16; + matches++; + break; + } while ((i = s->sums[i].chain) >= 0); + + null_hash: + backup = (int32)(offset - last_match); + /* We sometimes read 1 byte prior to last_match... */ + if (backup < 0) + backup = 0; + + /* Trim off the first byte from the checksum */ + more = offset + k < len; + map = (schar *)map_ptr(buf, offset - backup, k + more + backup) + + backup; + s1 -= map[0] + CHAR_OFFSET; + s2 -= k * (map[0]+CHAR_OFFSET); + + /* Add on the next byte (if there is one) to the checksum */ + if (more) { + s1 += map[k] + CHAR_OFFSET; + s2 += s1; + } else + --k; + + /* By matching early we avoid re-reading the + data 3 times in the case where a token + match comes a long way after last + match. The 3 reads are caused by the + running match, the checksum update and the + literal send. */ + if (backup >= s->blength+CHUNK_SIZE && end-offset > CHUNK_SIZE) + matched(f, s, buf, offset - s->blength, -2); + } while (++offset < end); + + matched(f, s, buf, len, -1); + map_ptr(buf, len-1, 1); +} + + +/** + * Scan through a origin file, looking for sections that match + * checksums from the generator, and transmit either literal or token + * data. + * + * Also calculates the MD4 checksum of the whole file, using the md + * accumulator. This is transmitted with the file as protection + * against corruption on the wire. + * + * @param s Checksums received from the generator. If s->count == + * 0, then there are actually no checksums for this file. + * + * @param len Length of the file to send. + **/ +void match_sums(int f, struct sum_struct *s, struct map_struct *buf, OFF_T len) +{ + last_match = 0; + false_alarms = 0; + hash_hits = 0; + matches = 0; + data_transfer = 0; + + sum_init(checksum_seed); + + if (append_mode > 0) { + if (append_mode == 2) { + OFF_T j = 0; + for (j = CHUNK_SIZE; j < s->flength; j += CHUNK_SIZE) { + if (buf && INFO_GTE(PROGRESS, 1)) + show_progress(last_match, buf->file_size); + sum_update(map_ptr(buf, last_match, CHUNK_SIZE), + CHUNK_SIZE); + last_match = j; + } + if (last_match < s->flength) { + int32 n = (int32)(s->flength - last_match); + if (buf && INFO_GTE(PROGRESS, 1)) + show_progress(last_match, buf->file_size); + sum_update(map_ptr(buf, last_match, n), n); + } + } + last_match = s->flength; + s->count = 0; + } + + if (len > 0 && s->count > 0) { + build_hash_table(s); + + if (DEBUG_GTE(DELTASUM, 2)) + rprintf(FINFO,"built hash table\n"); + + hash_search(f, s, buf, len); + + if (DEBUG_GTE(DELTASUM, 2)) + rprintf(FINFO,"done hash search\n"); + } else { + OFF_T j; + /* by doing this in pieces we avoid too many seeks */ + for (j = last_match + CHUNK_SIZE; j < len; j += CHUNK_SIZE) + matched(f, s, buf, j, -2); + matched(f, s, buf, len, -1); + } + + if (sum_end(sender_file_sum) != checksum_len) + overflow_exit("checksum_len"); /* Impossible... */ + + /* If we had a read error, send a bad checksum. We use all bits + * off as long as the checksum doesn't happen to be that, in + * which case we turn the last 0 bit into a 1. */ + if (buf && buf->status != 0) { + int i; + for (i = 0; i < checksum_len && sender_file_sum[i] == 0; i++) {} + memset(sender_file_sum, 0, checksum_len); + if (i == checksum_len) + sender_file_sum[i-1]++; + } + + if (DEBUG_GTE(DELTASUM, 2)) + rprintf(FINFO,"sending file_sum\n"); + write_buf(f, sender_file_sum, checksum_len); + + if (DEBUG_GTE(DELTASUM, 2)) { + rprintf(FINFO, "false_alarms=%d hash_hits=%d matches=%d\n", + false_alarms, hash_hits, matches); + } + + total_hash_hits += hash_hits; + total_false_alarms += false_alarms; + total_matches += matches; + stats.literal_data += data_transfer; +} + +void match_report(void) +{ + if (!DEBUG_GTE(DELTASUM, 1)) + return; + + rprintf(FINFO, + "total: matches=%d hash_hits=%d false_alarms=%d data=%s\n", + total_matches, total_hash_hits, total_false_alarms, + big_num(stats.literal_data)); +} diff --git a/rsync/mkproto.pl b/rsync/mkproto.pl new file mode 100644 index 0000000..cdeb2ea --- /dev/null +++ b/rsync/mkproto.pl @@ -0,0 +1,48 @@ +# generate prototypes for rsync + +$old_protos = ''; +if (open(IN, 'proto.h')) { + $old_protos = join('', ); + close IN; +} + +%FN_MAP = ( + BOOL => 'BOOL ', + CHAR => 'char ', + INTEGER => 'int ', + STRING => 'char *', +); + +$inheader = 0; +$protos = qq|/* This file is automatically generated with "make proto". DO NOT EDIT */\n\n|; + +while (<>) { + if ($inheader) { + if (/[)][ \t]*$/) { + $inheader = 0; + s/$/;/; + } + $protos .= $_; + } elsif (/^FN_(LOCAL|GLOBAL)_([^(]+)\(([^,()]+)/) { + $ret = $FN_MAP{$2}; + $func = $3; + $arg = $1 eq 'LOCAL' ? 'int module_id' : 'void'; + $protos .= "$ret$func($arg);\n"; + } elsif (/^static|^extern/ || /[;]/ || !/^[A-Za-z][A-Za-z0-9_]* /) { + ; + } elsif (/[(].*[)][ \t]*$/) { + s/$/;/; + $protos .= $_; + } elsif (/[(]/) { + $inheader = 1; + $protos .= $_; + } +} + +if ($old_protos ne $protos) { + open(OUT, '>proto.h') or die $!; + print OUT $protos; + close OUT; +} + +open(OUT, '>proto.h-tstamp') and close OUT; diff --git a/rsync/options.c b/rsync/options.c new file mode 100644 index 0000000..62dfe4f --- /dev/null +++ b/rsync/options.c @@ -0,0 +1,2880 @@ +/* + * Command-line (and received via daemon-socket) option parsing. + * + * Copyright (C) 1998-2001 Andrew Tridgell + * Copyright (C) 2000, 2001, 2002 Martin Pool + * Copyright (C) 2002-2014 Wayne Davison + * + * 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, visit the http://fsf.org website. + */ + +#include "rsync.h" +#include "itypes.h" +#include +#include + +extern int module_id; +extern int local_server; +extern int sanitize_paths; +extern int daemon_over_rsh; +extern unsigned int module_dirlen; +extern filter_rule_list filter_list; +extern filter_rule_list daemon_filter_list; + +#define NOT_SPECIFIED (-42) + +int make_backups = 0; + +/** + * If 1, send the whole file as literal data rather than trying to + * create an incremental diff. + * + * If -1, then look at whether we're local or remote and go by that. + * + * @sa disable_deltas_p() + **/ +int whole_file = -1; + +int append_mode = 0; +int keep_dirlinks = 0; +int copy_dirlinks = 0; +int copy_links = 0; +int preserve_links = 0; +int preserve_hard_links = 0; +int preserve_acls = 0; +int preserve_xattrs = 0; +int preserve_perms = 0; +int preserve_executability = 0; +int preserve_devices = 0; +int preserve_specials = 0; +int preserve_uid = 0; +int preserve_gid = 0; +int preserve_times = 0; +int update_only = 0; +int cvs_exclude = 0; +int dry_run = 0; +int do_xfers = 1; +int ignore_times = 0; +int delete_mode = 0; +int delete_during = 0; +int delete_before = 0; +int delete_after = 0; +int delete_excluded = 0; +int remove_source_files = 0; +int one_file_system = 0; +int protocol_version = PROTOCOL_VERSION; +int sparse_files = 0; +int preallocate_files = 0; +int do_compression = 0; +int def_compress_level = NOT_SPECIFIED; +int am_root = 0; /* 0 = normal, 1 = root, 2 = --super, -1 = --fake-super */ +int am_server = 0; +int am_sender = 0; +int am_starting_up = 1; +int relative_paths = -1; +int implied_dirs = 1; +int missing_args = 0; /* 0 = FERROR_XFER, 1 = ignore, 2 = delete */ +int numeric_ids = 0; +int msgs2stderr = 0; +int allow_8bit_chars = 0; +int force_delete = 0; +int io_timeout = 0; +int prune_empty_dirs = 0; +int use_qsort = 0; +char *files_from = NULL; +int filesfrom_fd = -1; +char *filesfrom_host = NULL; +int eol_nulls = 0; +int protect_args = -1; +int human_readable = 1; +int recurse = 0; +int allow_inc_recurse = 1; +int xfer_dirs = -1; +int am_daemon = 0; +int connect_timeout = 0; +int keep_partial = 0; +int safe_symlinks = 0; +int copy_unsafe_links = 0; +int munge_symlinks = 0; +int size_only = 0; +int daemon_bwlimit = 0; +int bwlimit = 0; +int fuzzy_basis = 0; +size_t bwlimit_writemax = 0; +int ignore_existing = 0; +int ignore_non_existing = 0; +int need_messages_from_generator = 0; +int max_delete = INT_MIN; +OFF_T max_size = -1; +OFF_T min_size = -1; +int ignore_errors = 0; +int modify_window = 0; +int blocking_io = -1; +int checksum_seed = 0; +int inplace = 0; +int delay_updates = 0; +long block_size = 0; /* "long" because popt can't set an int32. */ +char *skip_compress = NULL; +item_list dparam_list = EMPTY_ITEM_LIST; + +/** Network address family. **/ +int default_af_hint +#ifdef INET6 + = 0; /* Any protocol */ +#else + = AF_INET; /* Must use IPv4 */ +# ifdef AF_INET6 +# undef AF_INET6 +# endif +# define AF_INET6 AF_INET /* make -6 option a no-op */ +#endif + +/** Do not go into the background when run as --daemon. Good + * for debugging and required for running as a service on W32, + * or under Unix process-monitors. **/ +int no_detach +#if defined _WIN32 || defined __WIN32__ + = 1; +#else + = 0; +#endif + +int write_batch = 0; +int read_batch = 0; +int backup_dir_len = 0; +int backup_suffix_len; +unsigned int backup_dir_remainder; + +char *backup_suffix = NULL; +char *tmpdir = NULL; +char *partial_dir = NULL; +char *basis_dir[MAX_BASIS_DIRS+1]; +char *config_file = NULL; +char *shell_cmd = NULL; +char *logfile_name = NULL; +char *logfile_format = NULL; +char *stdout_format = NULL; +char *password_file = NULL; +char *rsync_path = RSYNC_PATH; +char *backup_dir = NULL; +char backup_dir_buf[MAXPATHLEN]; +char *sockopts = NULL; +char *usermap = NULL; +char *groupmap = NULL; +int rsync_port = 0; +int compare_dest = 0; +int copy_dest = 0; +int link_dest = 0; +int basis_dir_cnt = 0; +char *dest_option = NULL; + +static int remote_option_alloc = 0; +int remote_option_cnt = 0; +const char **remote_options = NULL; + +int quiet = 0; +int output_motd = 1; +int log_before_transfer = 0; +int stdout_format_has_i = 0; +int stdout_format_has_o_or_i = 0; +int logfile_format_has_i = 0; +int logfile_format_has_o_or_i = 0; +int always_checksum = 0; +int list_only = 0; + +#define MAX_BATCH_NAME_LEN 256 /* Must be less than MAXPATHLEN-13 */ +char *batch_name = NULL; + +int need_unsorted_flist = 0; +#ifdef ICONV_OPTION +char *iconv_opt = ICONV_OPTION; +#endif + +struct chmod_mode_struct *chmod_modes = NULL; + +static const char *debug_verbosity[] = { + /*0*/ NULL, + /*1*/ NULL, + /*2*/ "BIND,CMD,CONNECT,DEL,DELTASUM,DUP,FILTER,FLIST,ICONV", + /*3*/ "ACL,BACKUP,CONNECT2,DELTASUM2,DEL2,EXIT,FILTER2,FLIST2,FUZZY,GENR,OWN,RECV,SEND,TIME", + /*4*/ "CMD2,DELTASUM3,DEL3,EXIT2,FLIST3,ICONV2,OWN2,PROTO,TIME2", + /*5*/ "CHDIR,DELTASUM4,FLIST4,FUZZY2,HASH,HLINK", +}; + +#define MAX_VERBOSITY ((int)(sizeof debug_verbosity / sizeof debug_verbosity[0]) - 1) + +static const char *info_verbosity[1+MAX_VERBOSITY] = { + /*0*/ NULL, + /*1*/ "COPY,DEL,FLIST,MISC,NAME,STATS,SYMSAFE", + /*2*/ "BACKUP,MISC2,MOUNT,NAME2,REMOVE,SKIP", +}; + +#define MAX_OUT_LEVEL 4 /* The largest N allowed for any flagN word. */ + +short info_levels[COUNT_INFO], debug_levels[COUNT_DEBUG]; + +#define DEFAULT_PRIORITY 0 /* Default/implied/--verbose set values. */ +#define HELP_PRIORITY 1 /* The help output uses this level. */ +#define USER_PRIORITY 2 /* User-specified via --info or --debug */ +#define LIMIT_PRIORITY 3 /* Overriding priority when limiting values. */ + +#define W_CLI (1<<0) /* client side */ +#define W_SRV (1<<1) /* server side */ +#define W_SND (1<<2) /* sending side */ +#define W_REC (1<<3) /* receiving side */ + +struct output_struct { + char *name; /* The name of the info/debug flag. */ + char *help; /* The description of the info/debug flag. */ + uchar namelen; /* The length of the name string. */ + uchar flag; /* The flag's value, for consistency check. */ + uchar where; /* Bits indicating where the flag is used. */ + uchar priority; /* See *_PRIORITY defines. */ +}; + +#define INFO_WORD(flag, where, help) { #flag, help, sizeof #flag - 1, INFO_##flag, where, 0 } + +static struct output_struct info_words[COUNT_INFO+1] = { + INFO_WORD(BACKUP, W_REC, "Mention files backed up"), + INFO_WORD(COPY, W_REC, "Mention files copied locally on the receiving side"), + INFO_WORD(DEL, W_REC, "Mention deletions on the receiving side"), + INFO_WORD(FLIST, W_CLI, "Mention file-list receiving/sending (levels 1-2)"), + INFO_WORD(MISC, W_SND|W_REC, "Mention miscellaneous information (levels 1-2)"), + INFO_WORD(MOUNT, W_SND|W_REC, "Mention mounts that were found or skipped"), + INFO_WORD(NAME, W_SND|W_REC, "Mention 1) updated file/dir names, 2) unchanged names"), + INFO_WORD(PROGRESS, W_CLI, "Mention 1) per-file progress or 2) total transfer progress"), + INFO_WORD(REMOVE, W_SND, "Mention files removed on the sending side"), + INFO_WORD(SKIP, W_REC, "Mention files that are skipped due to options used"), + INFO_WORD(STATS, W_CLI|W_SRV, "Mention statistics at end of run (levels 1-3)"), + INFO_WORD(SYMSAFE, W_SND|W_REC, "Mention symlinks that are unsafe"), + { NULL, "--info", 0, 0, 0, 0 } +}; + +#define DEBUG_WORD(flag, where, help) { #flag, help, sizeof #flag - 1, DEBUG_##flag, where, 0 } + +static struct output_struct debug_words[COUNT_DEBUG+1] = { + DEBUG_WORD(ACL, W_SND|W_REC, "Debug extra ACL info"), + DEBUG_WORD(BACKUP, W_REC, "Debug backup actions (levels 1-2)"), + DEBUG_WORD(BIND, W_CLI, "Debug socket bind actions"), + DEBUG_WORD(CHDIR, W_CLI|W_SRV, "Debug when the current directory changes"), + DEBUG_WORD(CONNECT, W_CLI, "Debug connection events (levels 1-2)"), + DEBUG_WORD(CMD, W_CLI, "Debug commands+options that are issued (levels 1-2)"), + DEBUG_WORD(DEL, W_REC, "Debug delete actions (levels 1-3)"), + DEBUG_WORD(DELTASUM, W_SND|W_REC, "Debug delta-transfer checksumming (levels 1-4)"), + DEBUG_WORD(DUP, W_REC, "Debug weeding of duplicate names"), + DEBUG_WORD(EXIT, W_CLI|W_SRV, "Debug exit events (levels 1-3)"), + DEBUG_WORD(FILTER, W_SND|W_REC, "Debug filter actions (levels 1-2)"), + DEBUG_WORD(FLIST, W_SND|W_REC, "Debug file-list operations (levels 1-4)"), + DEBUG_WORD(FUZZY, W_REC, "Debug fuzzy scoring (levels 1-2)"), + DEBUG_WORD(GENR, W_REC, "Debug generator functions"), + DEBUG_WORD(HASH, W_SND|W_REC, "Debug hashtable code"), + DEBUG_WORD(HLINK, W_SND|W_REC, "Debug hard-link actions (levels 1-3)"), + DEBUG_WORD(ICONV, W_CLI|W_SRV, "Debug iconv character conversions (levels 1-2)"), + DEBUG_WORD(IO, W_CLI|W_SRV, "Debug I/O routines (levels 1-4)"), + DEBUG_WORD(OWN, W_REC, "Debug ownership changes in users & groups (levels 1-2)"), + DEBUG_WORD(PROTO, W_CLI|W_SRV, "Debug protocol information"), + DEBUG_WORD(RECV, W_REC, "Debug receiver functions"), + DEBUG_WORD(SEND, W_SND, "Debug sender functions"), + DEBUG_WORD(TIME, W_REC, "Debug setting of modified times (levels 1-2)"), + { NULL, "--debug", 0, 0, 0, 0 } +}; + +static int verbose = 0; +static int do_stats = 0; +static int do_progress = 0; +static int daemon_opt; /* sets am_daemon after option error-reporting */ +static int omit_dir_times = 0; +static int omit_link_times = 0; +static int F_option_cnt = 0; +static int modify_window_set; +static int itemize_changes = 0; +static int refused_delete, refused_archive_part, refused_compress; +static int refused_partial, refused_progress, refused_delete_before; +static int refused_delete_during; +static int refused_inplace, refused_no_iconv; +static BOOL usermap_via_chown, groupmap_via_chown; +#ifdef HAVE_SETVBUF +static char *outbuf_mode; +#endif +static char *bwlimit_arg, *max_size_arg, *min_size_arg; +static char tmp_partialdir[] = ".~tmp~"; + +/** Local address to bind. As a character string because it's + * interpreted by the IPv6 layer: should be a numeric IP4 or IP6 + * address, or a hostname. **/ +char *bind_address; + +static void output_item_help(struct output_struct *words); + +/* This constructs a string that represents all the options set for either + * the --info or --debug setting, skipping any implied options (by -v, etc.). + * This is used both when conveying the user's options to the server, and + * when the help output wants to tell the user what options are implied. */ +static char *make_output_option(struct output_struct *words, short *levels, uchar where) +{ + char *str = words == info_words ? "--info=" : "--debug="; + int j, counts[MAX_OUT_LEVEL+1], pos, skipped = 0, len = 0, max = 0, lev = 0; + int word_count = words == info_words ? COUNT_INFO : COUNT_DEBUG; + char *buf; + + memset(counts, 0, sizeof counts); + + for (j = 0; words[j].name; j++) { + if (words[j].flag != j) { + rprintf(FERROR, "rsync: internal error on %s%s: %d != %d\n", + words == info_words ? "INFO_" : "DEBUG_", + words[j].name, words[j].flag, j); + exit_cleanup(RERR_UNSUPPORTED); + } + if (!(words[j].where & where)) + continue; + if (words[j].priority == DEFAULT_PRIORITY) { + /* Implied items don't need to be mentioned. */ + skipped++; + continue; + } + len += len ? 1 : strlen(str); + len += strlen(words[j].name); + len += levels[j] == 1 ? 0 : 1; + + if (words[j].priority == HELP_PRIORITY) + continue; /* no abbreviating for help */ + + assert(levels[j] <= MAX_OUT_LEVEL); + if (++counts[levels[j]] > max) { + /* Determine which level has the most items. */ + lev = levels[j]; + max = counts[lev]; + } + } + + /* Sanity check the COUNT_* define against the length of the table. */ + if (j != word_count) { + rprintf(FERROR, "rsync: internal error: %s is wrong! (%d != %d)\n", + words == info_words ? "COUNT_INFO" : "COUNT_DEBUG", + j, word_count); + exit_cleanup(RERR_UNSUPPORTED); + } + + if (!len) + return NULL; + + len++; + if (!(buf = new_array(char, len))) + out_of_memory("make_output_option"); + pos = 0; + + if (skipped || max < 5) + lev = -1; + else { + if (lev == 0) + pos += snprintf(buf, len, "%sNONE", str); + else if (lev == 1) + pos += snprintf(buf, len, "%sALL", str); + else + pos += snprintf(buf, len, "%sALL%d", str, lev); + } + + for (j = 0; words[j].name && pos < len; j++) { + if (words[j].priority == DEFAULT_PRIORITY || levels[j] == lev || !(words[j].where & where)) + continue; + if (pos) + buf[pos++] = ','; + else + pos += strlcpy(buf+pos, str, len-pos); + if (pos < len) + pos += strlcpy(buf+pos, words[j].name, len-pos); + /* Level 1 is implied by the name alone. */ + if (levels[j] != 1 && pos < len) + buf[pos++] = '0' + levels[j]; + } + + buf[pos] = '\0'; + + return buf; +} + +static void parse_output_words(struct output_struct *words, short *levels, + const char *str, uchar priority) +{ + const char *s; + int j, len, lev; + + if (!str) + return; + + while (*str) { + if ((s = strchr(str, ',')) != NULL) + len = s++ - str; + else + len = strlen(str); + while (len && isDigit(str+len-1)) + len--; + lev = isDigit(str+len) ? atoi(str+len) : 1; + if (lev > MAX_OUT_LEVEL) + lev = MAX_OUT_LEVEL; + if (len == 4 && strncasecmp(str, "help", 4) == 0) { + output_item_help(words); + exit_cleanup(0); + } + if (len == 4 && strncasecmp(str, "none", 4) == 0) + len = lev = 0; + else if (len == 3 && strncasecmp(str, "all", 3) == 0) + len = 0; + for (j = 0; words[j].name; j++) { + if (!len + || (len == words[j].namelen && strncasecmp(str, words[j].name, len) == 0)) { + if (priority >= words[j].priority) { + words[j].priority = priority; + levels[j] = lev; + } + if (len) + break; + } + } + if (len && !words[j].name) { + rprintf(FERROR, "Unknown %s item: \"%.*s\"\n", + words[j].help, len, str); + exit_cleanup(RERR_SYNTAX); + } + if (!s) + break; + str = s; + } +} + +/* Tell the user what all the info or debug flags mean. */ +static void output_item_help(struct output_struct *words) +{ + short *levels = words == info_words ? info_levels : debug_levels; + const char **verbosity = words == info_words ? info_verbosity : debug_verbosity; + char buf[128], *opt, *fmt = "%-10s %s\n"; + int j; + + reset_output_levels(); + + rprintf(FINFO, "Use OPT or OPT1 for level 1 output, OPT2 for level 2, etc.; OPT0 silences.\n"); + rprintf(FINFO, "\n"); + for (j = 0; words[j].name; j++) + rprintf(FINFO, fmt, words[j].name, words[j].help); + rprintf(FINFO, "\n"); + + snprintf(buf, sizeof buf, "Set all %s options (e.g. all%d)", + words[j].help, MAX_OUT_LEVEL); + rprintf(FINFO, fmt, "ALL", buf); + + snprintf(buf, sizeof buf, "Silence all %s options (same as all0)", + words[j].help); + rprintf(FINFO, fmt, "NONE", buf); + + rprintf(FINFO, fmt, "HELP", "Output this help message"); + rprintf(FINFO, "\n"); + rprintf(FINFO, "Options added for each increase in verbose level:\n"); + + for (j = 1; j <= MAX_VERBOSITY; j++) { + parse_output_words(words, levels, verbosity[j], HELP_PRIORITY); + opt = make_output_option(words, levels, W_CLI|W_SRV|W_SND|W_REC); + if (opt) { + rprintf(FINFO, "%d) %s\n", j, strchr(opt, '=')+1); + free(opt); + } + reset_output_levels(); + } +} + +/* The --verbose option now sets info+debug flags. */ +static void set_output_verbosity(int level, uchar priority) +{ + int j; + + if (level > MAX_VERBOSITY) + level = MAX_VERBOSITY; + + for (j = 1; j <= level; j++) { + parse_output_words(info_words, info_levels, info_verbosity[j], priority); + parse_output_words(debug_words, debug_levels, debug_verbosity[j], priority); + } +} + +/* Limit the info+debug flag levels given a verbose-option level limit. */ +void limit_output_verbosity(int level) +{ + short info_limits[COUNT_INFO], debug_limits[COUNT_DEBUG]; + int j; + + if (level > MAX_VERBOSITY) + return; + + memset(info_limits, 0, sizeof info_limits); + memset(debug_limits, 0, sizeof debug_limits); + + /* Compute the level limits in the above arrays. */ + for (j = 1; j <= level; j++) { + parse_output_words(info_words, info_limits, info_verbosity[j], LIMIT_PRIORITY); + parse_output_words(debug_words, debug_limits, debug_verbosity[j], LIMIT_PRIORITY); + } + + for (j = 0; j < COUNT_INFO; j++) { + if (info_levels[j] > info_limits[j]) + info_levels[j] = info_limits[j]; + } + + for (j = 0; j < COUNT_DEBUG; j++) { + if (debug_levels[j] > debug_limits[j]) + debug_levels[j] = debug_limits[j]; + } +} + +void reset_output_levels(void) +{ + int j; + + memset(info_levels, 0, sizeof info_levels); + memset(debug_levels, 0, sizeof debug_levels); + + for (j = 0; j < COUNT_INFO; j++) + info_words[j].priority = DEFAULT_PRIORITY; + + for (j = 0; j < COUNT_DEBUG; j++) + debug_words[j].priority = DEFAULT_PRIORITY; +} + +void negate_output_levels(void) +{ + int j; + + for (j = 0; j < COUNT_INFO; j++) + info_levels[j] *= -1; + + for (j = 0; j < COUNT_DEBUG; j++) + debug_levels[j] *= -1; +} + +static void print_rsync_version(enum logcode f) +{ + char *subprotocol = ""; + char const *got_socketpair = "no "; + char const *have_inplace = "no "; + char const *hardlinks = "no "; + char const *prealloc = "no "; + char const *symtimes = "no "; + char const *acls = "no "; + char const *xattrs = "no "; + char const *links = "no "; + char const *iconv = "no "; + char const *ipv6 = "no "; + STRUCT_STAT *dumstat; + +#if SUBPROTOCOL_VERSION != 0 + if (asprintf(&subprotocol, ".PR%d", SUBPROTOCOL_VERSION) < 0) + out_of_memory("print_rsync_version"); +#endif +#ifdef HAVE_SOCKETPAIR + got_socketpair = ""; +#endif +#ifdef HAVE_FTRUNCATE + have_inplace = ""; +#endif +#ifdef SUPPORT_HARD_LINKS + hardlinks = ""; +#endif +#ifdef SUPPORT_PREALLOCATION + prealloc = ""; +#endif +#ifdef SUPPORT_ACLS + acls = ""; +#endif +#ifdef SUPPORT_XATTRS + xattrs = ""; +#endif +#ifdef SUPPORT_LINKS + links = ""; +#endif +#ifdef INET6 + ipv6 = ""; +#endif +#ifdef ICONV_OPTION + iconv = ""; +#endif +#ifdef CAN_SET_SYMLINK_TIMES + symtimes = ""; +#endif + + rprintf(f, "%s version %s protocol version %d%s\n", + RSYNC_NAME, RSYNC_VERSION, PROTOCOL_VERSION, subprotocol); + rprintf(f, "Copyright (C) 1996-2014 by Andrew Tridgell, Wayne Davison, and others.\n"); + rprintf(f, "Web site: http://rsync.samba.org/\n"); + rprintf(f, "Capabilities:\n"); + rprintf(f, " %d-bit files, %d-bit inums, %d-bit timestamps, %d-bit long ints,\n", + (int)(sizeof (OFF_T) * 8), + (int)(sizeof dumstat->st_ino * 8), /* Don't check ino_t! */ + (int)(sizeof (time_t) * 8), + (int)(sizeof (int64) * 8)); + rprintf(f, " %ssocketpairs, %shardlinks, %ssymlinks, %sIPv6, batchfiles, %sinplace,\n", + got_socketpair, hardlinks, links, ipv6, have_inplace); + rprintf(f, " %sappend, %sACLs, %sxattrs, %siconv, %ssymtimes, %sprealloc\n", + have_inplace, acls, xattrs, iconv, symtimes, prealloc); + +#ifdef MAINTAINER_MODE + rprintf(f, "Panic Action: \"%s\"\n", get_panic_action()); +#endif + +#if SIZEOF_INT64 < 8 + rprintf(f, "WARNING: no 64-bit integers on this platform!\n"); +#endif + if (sizeof (int64) != SIZEOF_INT64) { + rprintf(f, + "WARNING: size mismatch in SIZEOF_INT64 define (%d != %d)\n", + (int) SIZEOF_INT64, (int) sizeof (int64)); + } + + rprintf(f,"\n"); + rprintf(f,"rsync comes with ABSOLUTELY NO WARRANTY. This is free software, and you\n"); + rprintf(f,"are welcome to redistribute it under certain conditions. See the GNU\n"); + rprintf(f,"General Public Licence for details.\n"); +} + + +void usage(enum logcode F) +{ + print_rsync_version(F); + + rprintf(F,"\n"); + rprintf(F,"rsync is a file transfer program capable of efficient remote update\n"); + rprintf(F,"via a fast differencing algorithm.\n"); + + rprintf(F,"\n"); + rprintf(F,"Usage: rsync [OPTION]... SRC [SRC]... DEST\n"); + rprintf(F," or rsync [OPTION]... SRC [SRC]... [USER@]HOST:DEST\n"); + rprintf(F," or rsync [OPTION]... SRC [SRC]... [USER@]HOST::DEST\n"); + rprintf(F," or rsync [OPTION]... SRC [SRC]... rsync://[USER@]HOST[:PORT]/DEST\n"); + rprintf(F," or rsync [OPTION]... [USER@]HOST:SRC [DEST]\n"); + rprintf(F," or rsync [OPTION]... [USER@]HOST::SRC [DEST]\n"); + rprintf(F," or rsync [OPTION]... rsync://[USER@]HOST[:PORT]/SRC [DEST]\n"); + rprintf(F,"The ':' usages connect via remote shell, while '::' & 'rsync://' usages connect\n"); + rprintf(F,"to an rsync daemon, and require SRC or DEST to start with a module name.\n"); + rprintf(F,"\n"); + rprintf(F,"Options\n"); + rprintf(F," -v, --verbose increase verbosity\n"); + rprintf(F," --info=FLAGS fine-grained informational verbosity\n"); + rprintf(F," --debug=FLAGS fine-grained debug verbosity\n"); + rprintf(F," --msgs2stderr special output handling for debugging\n"); + rprintf(F," -q, --quiet suppress non-error messages\n"); + rprintf(F," --no-motd suppress daemon-mode MOTD (see manpage caveat)\n"); + rprintf(F," -c, --checksum skip based on checksum, not mod-time & size\n"); + rprintf(F," -a, --archive archive mode; equals -rlptgoD (no -H,-A,-X)\n"); + rprintf(F," --no-OPTION turn off an implied OPTION (e.g. --no-D)\n"); + rprintf(F," -r, --recursive recurse into directories\n"); + rprintf(F," -R, --relative use relative path names\n"); + rprintf(F," --no-implied-dirs don't send implied dirs with --relative\n"); + rprintf(F," -b, --backup make backups (see --suffix & --backup-dir)\n"); + rprintf(F," --backup-dir=DIR make backups into hierarchy based in DIR\n"); + rprintf(F," --suffix=SUFFIX set backup suffix (default %s w/o --backup-dir)\n",BACKUP_SUFFIX); + rprintf(F," -u, --update skip files that are newer on the receiver\n"); + rprintf(F," --inplace update destination files in-place (SEE MAN PAGE)\n"); + rprintf(F," --append append data onto shorter files\n"); + rprintf(F," --append-verify like --append, but with old data in file checksum\n"); + rprintf(F," -d, --dirs transfer directories without recursing\n"); + rprintf(F," -l, --links copy symlinks as symlinks\n"); + rprintf(F," -L, --copy-links transform symlink into referent file/dir\n"); + rprintf(F," --copy-unsafe-links only \"unsafe\" symlinks are transformed\n"); + rprintf(F," --safe-links ignore symlinks that point outside the source tree\n"); + rprintf(F," --munge-links munge symlinks to make them safer (but unusable)\n"); + rprintf(F," -k, --copy-dirlinks transform symlink to a dir into referent dir\n"); + rprintf(F," -K, --keep-dirlinks treat symlinked dir on receiver as dir\n"); + rprintf(F," -H, --hard-links preserve hard links\n"); + rprintf(F," -p, --perms preserve permissions\n"); + rprintf(F," -E, --executability preserve the file's executability\n"); + rprintf(F," --chmod=CHMOD affect file and/or directory permissions\n"); +#ifdef SUPPORT_ACLS + rprintf(F," -A, --acls preserve ACLs (implies --perms)\n"); +#endif +#ifdef SUPPORT_XATTRS + rprintf(F," -X, --xattrs preserve extended attributes\n"); +#endif + rprintf(F," -o, --owner preserve owner (super-user only)\n"); + rprintf(F," -g, --group preserve group\n"); + rprintf(F," --devices preserve device files (super-user only)\n"); + rprintf(F," --specials preserve special files\n"); + rprintf(F," -D same as --devices --specials\n"); + rprintf(F," -t, --times preserve modification times\n"); + rprintf(F," -O, --omit-dir-times omit directories from --times\n"); + rprintf(F," -J, --omit-link-times omit symlinks from --times\n"); + rprintf(F," --super receiver attempts super-user activities\n"); +#ifdef SUPPORT_XATTRS + rprintf(F," --fake-super store/recover privileged attrs using xattrs\n"); +#endif + rprintf(F," -S, --sparse handle sparse files efficiently\n"); +#ifdef SUPPORT_PREALLOCATION + rprintf(F," --preallocate allocate dest files before writing them\n"); +#else + rprintf(F," --preallocate pre-allocate dest files on remote receiver\n"); +#endif + rprintf(F," -n, --dry-run perform a trial run with no changes made\n"); + rprintf(F," -W, --whole-file copy files whole (without delta-xfer algorithm)\n"); + rprintf(F," -x, --one-file-system don't cross filesystem boundaries\n"); + rprintf(F," -B, --block-size=SIZE force a fixed checksum block-size\n"); + rprintf(F," -e, --rsh=COMMAND specify the remote shell to use\n"); + rprintf(F," --rsync-path=PROGRAM specify the rsync to run on the remote machine\n"); + rprintf(F," --existing skip creating new files on receiver\n"); + rprintf(F," --ignore-existing skip updating files that already exist on receiver\n"); + rprintf(F," --remove-source-files sender removes synchronized files (non-dirs)\n"); + rprintf(F," --del an alias for --delete-during\n"); + rprintf(F," --delete delete extraneous files from destination dirs\n"); + rprintf(F," --delete-before receiver deletes before transfer, not during\n"); + rprintf(F," --delete-during receiver deletes during the transfer\n"); + rprintf(F," --delete-delay find deletions during, delete after\n"); + rprintf(F," --delete-after receiver deletes after transfer, not during\n"); + rprintf(F," --delete-excluded also delete excluded files from destination dirs\n"); + rprintf(F," --ignore-missing-args ignore missing source args without error\n"); + rprintf(F," --delete-missing-args delete missing source args from destination\n"); + rprintf(F," --ignore-errors delete even if there are I/O errors\n"); + rprintf(F," --force force deletion of directories even if not empty\n"); + rprintf(F," --max-delete=NUM don't delete more than NUM files\n"); + rprintf(F," --max-size=SIZE don't transfer any file larger than SIZE\n"); + rprintf(F," --min-size=SIZE don't transfer any file smaller than SIZE\n"); + rprintf(F," --partial keep partially transferred files\n"); + rprintf(F," --partial-dir=DIR put a partially transferred file into DIR\n"); + rprintf(F," --delay-updates put all updated files into place at transfer's end\n"); + rprintf(F," -m, --prune-empty-dirs prune empty directory chains from the file-list\n"); + rprintf(F," --numeric-ids don't map uid/gid values by user/group name\n"); + rprintf(F," --usermap=STRING custom username mapping\n"); + rprintf(F," --groupmap=STRING custom groupname mapping\n"); + rprintf(F," --chown=USER:GROUP simple username/groupname mapping\n"); + rprintf(F," --timeout=SECONDS set I/O timeout in seconds\n"); + rprintf(F," --contimeout=SECONDS set daemon connection timeout in seconds\n"); + rprintf(F," -I, --ignore-times don't skip files that match in size and mod-time\n"); + rprintf(F," -M, --remote-option=OPTION send OPTION to the remote side only\n"); + rprintf(F," --size-only skip files that match in size\n"); + rprintf(F," --modify-window=NUM compare mod-times with reduced accuracy\n"); + rprintf(F," -T, --temp-dir=DIR create temporary files in directory DIR\n"); + rprintf(F," -y, --fuzzy find similar file for basis if no dest file\n"); + rprintf(F," --compare-dest=DIR also compare destination files relative to DIR\n"); + rprintf(F," --copy-dest=DIR ... and include copies of unchanged files\n"); + rprintf(F," --link-dest=DIR hardlink to files in DIR when unchanged\n"); + rprintf(F," -z, --compress compress file data during the transfer\n"); + rprintf(F," --compress-level=NUM explicitly set compression level\n"); + rprintf(F," --skip-compress=LIST skip compressing files with a suffix in LIST\n"); + rprintf(F," -C, --cvs-exclude auto-ignore files the same way CVS does\n"); + rprintf(F," -f, --filter=RULE add a file-filtering RULE\n"); + rprintf(F," -F same as --filter='dir-merge /.rsync-filter'\n"); + rprintf(F," repeated: --filter='- .rsync-filter'\n"); + rprintf(F," --exclude=PATTERN exclude files matching PATTERN\n"); + rprintf(F," --exclude-from=FILE read exclude patterns from FILE\n"); + rprintf(F," --include=PATTERN don't exclude files matching PATTERN\n"); + rprintf(F," --include-from=FILE read include patterns from FILE\n"); + rprintf(F," --files-from=FILE read list of source-file names from FILE\n"); + rprintf(F," -0, --from0 all *-from/filter files are delimited by 0s\n"); + rprintf(F," -s, --protect-args no space-splitting; only wildcard special-chars\n"); + rprintf(F," --address=ADDRESS bind address for outgoing socket to daemon\n"); + rprintf(F," --port=PORT specify double-colon alternate port number\n"); + rprintf(F," --sockopts=OPTIONS specify custom TCP options\n"); + rprintf(F," --blocking-io use blocking I/O for the remote shell\n"); + rprintf(F," --stats give some file-transfer stats\n"); + rprintf(F," -8, --8-bit-output leave high-bit chars unescaped in output\n"); + rprintf(F," -h, --human-readable output numbers in a human-readable format\n"); + rprintf(F," --progress show progress during transfer\n"); + rprintf(F," -P same as --partial --progress\n"); + rprintf(F," -i, --itemize-changes output a change-summary for all updates\n"); + rprintf(F," --out-format=FORMAT output updates using the specified FORMAT\n"); + rprintf(F," --log-file=FILE log what we're doing to the specified FILE\n"); + rprintf(F," --log-file-format=FMT log updates using the specified FMT\n"); + rprintf(F," --password-file=FILE read daemon-access password from FILE\n"); + rprintf(F," --list-only list the files instead of copying them\n"); + rprintf(F," --bwlimit=RATE limit socket I/O bandwidth\n"); +#ifdef HAVE_SETVBUF + rprintf(F," --outbuf=N|L|B set output buffering to None, Line, or Block\n"); +#endif + rprintf(F," --write-batch=FILE write a batched update to FILE\n"); + rprintf(F," --only-write-batch=FILE like --write-batch but w/o updating destination\n"); + rprintf(F," --read-batch=FILE read a batched update from FILE\n"); + rprintf(F," --protocol=NUM force an older protocol version to be used\n"); +#ifdef ICONV_OPTION + rprintf(F," --iconv=CONVERT_SPEC request charset conversion of filenames\n"); +#endif + rprintf(F," --checksum-seed=NUM set block/file checksum seed (advanced)\n"); + rprintf(F," -4, --ipv4 prefer IPv4\n"); + rprintf(F," -6, --ipv6 prefer IPv6\n"); + rprintf(F," --version print version number\n"); + rprintf(F,"(-h) --help show this help (-h is --help only if used alone)\n"); + + rprintf(F,"\n"); + rprintf(F,"Use \"rsync --daemon --help\" to see the daemon-mode command-line options.\n"); + rprintf(F,"Please see the rsync(1) and rsyncd.conf(5) man pages for full documentation.\n"); + rprintf(F,"See http://rsync.samba.org/ for updates, bug reports, and answers\n"); +} + +enum {OPT_VERSION = 1000, OPT_DAEMON, OPT_SENDER, OPT_EXCLUDE, OPT_EXCLUDE_FROM, + OPT_FILTER, OPT_COMPARE_DEST, OPT_COPY_DEST, OPT_LINK_DEST, OPT_HELP, + OPT_INCLUDE, OPT_INCLUDE_FROM, OPT_MODIFY_WINDOW, OPT_MIN_SIZE, OPT_CHMOD, + OPT_READ_BATCH, OPT_WRITE_BATCH, OPT_ONLY_WRITE_BATCH, OPT_MAX_SIZE, + OPT_NO_D, OPT_APPEND, OPT_NO_ICONV, OPT_INFO, OPT_DEBUG, + OPT_USERMAP, OPT_GROUPMAP, OPT_CHOWN, OPT_BWLIMIT, + OPT_SERVER, OPT_REFUSED_BASE = 9000}; + +static struct poptOption long_options[] = { + /* longName, shortName, argInfo, argPtr, value, descrip, argDesc */ + {"help", 0, POPT_ARG_NONE, 0, OPT_HELP, 0, 0 }, + {"version", 0, POPT_ARG_NONE, 0, OPT_VERSION, 0, 0}, + {"verbose", 'v', POPT_ARG_NONE, 0, 'v', 0, 0 }, + {"no-verbose", 0, POPT_ARG_VAL, &verbose, 0, 0, 0 }, + {"no-v", 0, POPT_ARG_VAL, &verbose, 0, 0, 0 }, + {"info", 0, POPT_ARG_STRING, 0, OPT_INFO, 0, 0 }, + {"debug", 0, POPT_ARG_STRING, 0, OPT_DEBUG, 0, 0 }, + {"msgs2stderr", 0, POPT_ARG_NONE, &msgs2stderr, 0, 0, 0 }, + {"quiet", 'q', POPT_ARG_NONE, 0, 'q', 0, 0 }, + {"motd", 0, POPT_ARG_VAL, &output_motd, 1, 0, 0 }, + {"no-motd", 0, POPT_ARG_VAL, &output_motd, 0, 0, 0 }, + {"stats", 0, POPT_ARG_NONE, &do_stats, 0, 0, 0 }, + {"human-readable", 'h', POPT_ARG_NONE, 0, 'h', 0, 0}, + {"no-human-readable",0, POPT_ARG_VAL, &human_readable, 0, 0, 0}, + {"no-h", 0, POPT_ARG_VAL, &human_readable, 0, 0, 0}, + {"dry-run", 'n', POPT_ARG_NONE, &dry_run, 0, 0, 0 }, + {"archive", 'a', POPT_ARG_NONE, 0, 'a', 0, 0 }, + {"recursive", 'r', POPT_ARG_VAL, &recurse, 2, 0, 0 }, + {"no-recursive", 0, POPT_ARG_VAL, &recurse, 0, 0, 0 }, + {"no-r", 0, POPT_ARG_VAL, &recurse, 0, 0, 0 }, + {"inc-recursive", 0, POPT_ARG_VAL, &allow_inc_recurse, 1, 0, 0 }, + {"no-inc-recursive", 0, POPT_ARG_VAL, &allow_inc_recurse, 0, 0, 0 }, + {"i-r", 0, POPT_ARG_VAL, &allow_inc_recurse, 1, 0, 0 }, + {"no-i-r", 0, POPT_ARG_VAL, &allow_inc_recurse, 0, 0, 0 }, + {"dirs", 'd', POPT_ARG_VAL, &xfer_dirs, 2, 0, 0 }, + {"no-dirs", 0, POPT_ARG_VAL, &xfer_dirs, 0, 0, 0 }, + {"no-d", 0, POPT_ARG_VAL, &xfer_dirs, 0, 0, 0 }, + {"old-dirs", 0, POPT_ARG_VAL, &xfer_dirs, 4, 0, 0 }, + {"old-d", 0, POPT_ARG_VAL, &xfer_dirs, 4, 0, 0 }, + {"perms", 'p', POPT_ARG_VAL, &preserve_perms, 1, 0, 0 }, + {"no-perms", 0, POPT_ARG_VAL, &preserve_perms, 0, 0, 0 }, + {"no-p", 0, POPT_ARG_VAL, &preserve_perms, 0, 0, 0 }, + {"executability", 'E', POPT_ARG_NONE, &preserve_executability, 0, 0, 0 }, + {"acls", 'A', POPT_ARG_NONE, 0, 'A', 0, 0 }, + {"no-acls", 0, POPT_ARG_VAL, &preserve_acls, 0, 0, 0 }, + {"no-A", 0, POPT_ARG_VAL, &preserve_acls, 0, 0, 0 }, + {"xattrs", 'X', POPT_ARG_NONE, 0, 'X', 0, 0 }, + {"no-xattrs", 0, POPT_ARG_VAL, &preserve_xattrs, 0, 0, 0 }, + {"no-X", 0, POPT_ARG_VAL, &preserve_xattrs, 0, 0, 0 }, + {"times", 't', POPT_ARG_VAL, &preserve_times, 1, 0, 0 }, + {"no-times", 0, POPT_ARG_VAL, &preserve_times, 0, 0, 0 }, + {"no-t", 0, POPT_ARG_VAL, &preserve_times, 0, 0, 0 }, + {"omit-dir-times", 'O', POPT_ARG_VAL, &omit_dir_times, 1, 0, 0 }, + {"no-omit-dir-times",0, POPT_ARG_VAL, &omit_dir_times, 0, 0, 0 }, + {"no-O", 0, POPT_ARG_VAL, &omit_dir_times, 0, 0, 0 }, + {"omit-link-times", 'J', POPT_ARG_VAL, &omit_link_times, 1, 0, 0 }, + {"no-omit-link-times",0, POPT_ARG_VAL, &omit_link_times, 0, 0, 0 }, + {"no-J", 0, POPT_ARG_VAL, &omit_link_times, 0, 0, 0 }, + {"modify-window", 0, POPT_ARG_INT, &modify_window, OPT_MODIFY_WINDOW, 0, 0 }, + {"super", 0, POPT_ARG_VAL, &am_root, 2, 0, 0 }, + {"no-super", 0, POPT_ARG_VAL, &am_root, 0, 0, 0 }, + {"fake-super", 0, POPT_ARG_VAL, &am_root, -1, 0, 0 }, + {"owner", 'o', POPT_ARG_VAL, &preserve_uid, 1, 0, 0 }, + {"no-owner", 0, POPT_ARG_VAL, &preserve_uid, 0, 0, 0 }, + {"no-o", 0, POPT_ARG_VAL, &preserve_uid, 0, 0, 0 }, + {"group", 'g', POPT_ARG_VAL, &preserve_gid, 1, 0, 0 }, + {"no-group", 0, POPT_ARG_VAL, &preserve_gid, 0, 0, 0 }, + {"no-g", 0, POPT_ARG_VAL, &preserve_gid, 0, 0, 0 }, + {0, 'D', POPT_ARG_NONE, 0, 'D', 0, 0 }, + {"no-D", 0, POPT_ARG_NONE, 0, OPT_NO_D, 0, 0 }, + {"devices", 0, POPT_ARG_VAL, &preserve_devices, 1, 0, 0 }, + {"no-devices", 0, POPT_ARG_VAL, &preserve_devices, 0, 0, 0 }, + {"specials", 0, POPT_ARG_VAL, &preserve_specials, 1, 0, 0 }, + {"no-specials", 0, POPT_ARG_VAL, &preserve_specials, 0, 0, 0 }, + {"links", 'l', POPT_ARG_VAL, &preserve_links, 1, 0, 0 }, + {"no-links", 0, POPT_ARG_VAL, &preserve_links, 0, 0, 0 }, + {"no-l", 0, POPT_ARG_VAL, &preserve_links, 0, 0, 0 }, + {"copy-links", 'L', POPT_ARG_NONE, ©_links, 0, 0, 0 }, + {"copy-unsafe-links",0, POPT_ARG_NONE, ©_unsafe_links, 0, 0, 0 }, + {"safe-links", 0, POPT_ARG_NONE, &safe_symlinks, 0, 0, 0 }, + {"munge-links", 0, POPT_ARG_VAL, &munge_symlinks, 1, 0, 0 }, + {"no-munge-links", 0, POPT_ARG_VAL, &munge_symlinks, 0, 0, 0 }, + {"copy-dirlinks", 'k', POPT_ARG_NONE, ©_dirlinks, 0, 0, 0 }, + {"keep-dirlinks", 'K', POPT_ARG_NONE, &keep_dirlinks, 0, 0, 0 }, + {"hard-links", 'H', POPT_ARG_NONE, 0, 'H', 0, 0 }, + {"no-hard-links", 0, POPT_ARG_VAL, &preserve_hard_links, 0, 0, 0 }, + {"no-H", 0, POPT_ARG_VAL, &preserve_hard_links, 0, 0, 0 }, + {"relative", 'R', POPT_ARG_VAL, &relative_paths, 1, 0, 0 }, + {"no-relative", 0, POPT_ARG_VAL, &relative_paths, 0, 0, 0 }, + {"no-R", 0, POPT_ARG_VAL, &relative_paths, 0, 0, 0 }, + {"implied-dirs", 0, POPT_ARG_VAL, &implied_dirs, 1, 0, 0 }, + {"no-implied-dirs", 0, POPT_ARG_VAL, &implied_dirs, 0, 0, 0 }, + {"i-d", 0, POPT_ARG_VAL, &implied_dirs, 1, 0, 0 }, + {"no-i-d", 0, POPT_ARG_VAL, &implied_dirs, 0, 0, 0 }, + {"chmod", 0, POPT_ARG_STRING, 0, OPT_CHMOD, 0, 0 }, + {"ignore-times", 'I', POPT_ARG_NONE, &ignore_times, 0, 0, 0 }, + {"size-only", 0, POPT_ARG_NONE, &size_only, 0, 0, 0 }, + {"one-file-system", 'x', POPT_ARG_NONE, 0, 'x', 0, 0 }, + {"no-one-file-system",0, POPT_ARG_VAL, &one_file_system, 0, 0, 0 }, + {"no-x", 0, POPT_ARG_VAL, &one_file_system, 0, 0, 0 }, + {"update", 'u', POPT_ARG_NONE, &update_only, 0, 0, 0 }, + {"existing", 0, POPT_ARG_NONE, &ignore_non_existing, 0, 0, 0 }, + {"ignore-non-existing",0,POPT_ARG_NONE, &ignore_non_existing, 0, 0, 0 }, + {"ignore-existing", 0, POPT_ARG_NONE, &ignore_existing, 0, 0, 0 }, + {"max-size", 0, POPT_ARG_STRING, &max_size_arg, OPT_MAX_SIZE, 0, 0 }, + {"min-size", 0, POPT_ARG_STRING, &min_size_arg, OPT_MIN_SIZE, 0, 0 }, + {"sparse", 'S', POPT_ARG_VAL, &sparse_files, 1, 0, 0 }, + {"no-sparse", 0, POPT_ARG_VAL, &sparse_files, 0, 0, 0 }, + {"no-S", 0, POPT_ARG_VAL, &sparse_files, 0, 0, 0 }, + {"preallocate", 0, POPT_ARG_NONE, &preallocate_files, 0, 0, 0}, + {"inplace", 0, POPT_ARG_VAL, &inplace, 1, 0, 0 }, + {"no-inplace", 0, POPT_ARG_VAL, &inplace, 0, 0, 0 }, + {"append", 0, POPT_ARG_NONE, 0, OPT_APPEND, 0, 0 }, + {"append-verify", 0, POPT_ARG_VAL, &append_mode, 2, 0, 0 }, + {"no-append", 0, POPT_ARG_VAL, &append_mode, 0, 0, 0 }, + {"del", 0, POPT_ARG_NONE, &delete_during, 0, 0, 0 }, + {"delete", 0, POPT_ARG_NONE, &delete_mode, 0, 0, 0 }, + {"delete-before", 0, POPT_ARG_NONE, &delete_before, 0, 0, 0 }, + {"delete-during", 0, POPT_ARG_VAL, &delete_during, 1, 0, 0 }, + {"delete-delay", 0, POPT_ARG_VAL, &delete_during, 2, 0, 0 }, + {"delete-after", 0, POPT_ARG_NONE, &delete_after, 0, 0, 0 }, + {"delete-excluded", 0, POPT_ARG_NONE, &delete_excluded, 0, 0, 0 }, + {"delete-missing-args",0,POPT_BIT_SET, &missing_args, 2, 0, 0 }, + {"ignore-missing-args",0,POPT_BIT_SET, &missing_args, 1, 0, 0 }, + {"remove-sent-files",0, POPT_ARG_VAL, &remove_source_files, 2, 0, 0 }, /* deprecated */ + {"remove-source-files",0,POPT_ARG_VAL, &remove_source_files, 1, 0, 0 }, + {"force", 0, POPT_ARG_VAL, &force_delete, 1, 0, 0 }, + {"no-force", 0, POPT_ARG_VAL, &force_delete, 0, 0, 0 }, + {"ignore-errors", 0, POPT_ARG_VAL, &ignore_errors, 1, 0, 0 }, + {"no-ignore-errors", 0, POPT_ARG_VAL, &ignore_errors, 0, 0, 0 }, + {"max-delete", 0, POPT_ARG_INT, &max_delete, 0, 0, 0 }, + {0, 'F', POPT_ARG_NONE, 0, 'F', 0, 0 }, + {"filter", 'f', POPT_ARG_STRING, 0, OPT_FILTER, 0, 0 }, + {"exclude", 0, POPT_ARG_STRING, 0, OPT_EXCLUDE, 0, 0 }, + {"include", 0, POPT_ARG_STRING, 0, OPT_INCLUDE, 0, 0 }, + {"exclude-from", 0, POPT_ARG_STRING, 0, OPT_EXCLUDE_FROM, 0, 0 }, + {"include-from", 0, POPT_ARG_STRING, 0, OPT_INCLUDE_FROM, 0, 0 }, + {"cvs-exclude", 'C', POPT_ARG_NONE, &cvs_exclude, 0, 0, 0 }, + {"whole-file", 'W', POPT_ARG_VAL, &whole_file, 1, 0, 0 }, + {"no-whole-file", 0, POPT_ARG_VAL, &whole_file, 0, 0, 0 }, + {"no-W", 0, POPT_ARG_VAL, &whole_file, 0, 0, 0 }, + {"checksum", 'c', POPT_ARG_VAL, &always_checksum, 1, 0, 0 }, + {"no-checksum", 0, POPT_ARG_VAL, &always_checksum, 0, 0, 0 }, + {"no-c", 0, POPT_ARG_VAL, &always_checksum, 0, 0, 0 }, + {"block-size", 'B', POPT_ARG_LONG, &block_size, 0, 0, 0 }, + {"compare-dest", 0, POPT_ARG_STRING, 0, OPT_COMPARE_DEST, 0, 0 }, + {"copy-dest", 0, POPT_ARG_STRING, 0, OPT_COPY_DEST, 0, 0 }, + {"link-dest", 0, POPT_ARG_STRING, 0, OPT_LINK_DEST, 0, 0 }, + {"fuzzy", 'y', POPT_ARG_NONE, 0, 'y', 0, 0 }, + {"no-fuzzy", 0, POPT_ARG_VAL, &fuzzy_basis, 0, 0, 0 }, + {"no-y", 0, POPT_ARG_VAL, &fuzzy_basis, 0, 0, 0 }, + {"compress", 'z', POPT_ARG_NONE, 0, 'z', 0, 0 }, + {"old-compress", 0, POPT_ARG_VAL, &do_compression, 1, 0, 0 }, + {"new-compress", 0, POPT_ARG_VAL, &do_compression, 2, 0, 0 }, + {"no-compress", 0, POPT_ARG_VAL, &do_compression, 0, 0, 0 }, + {"no-z", 0, POPT_ARG_VAL, &do_compression, 0, 0, 0 }, + {"skip-compress", 0, POPT_ARG_STRING, &skip_compress, 0, 0, 0 }, + {"compress-level", 0, POPT_ARG_INT, &def_compress_level, 0, 0, 0 }, + {0, 'P', POPT_ARG_NONE, 0, 'P', 0, 0 }, + {"progress", 0, POPT_ARG_VAL, &do_progress, 1, 0, 0 }, + {"no-progress", 0, POPT_ARG_VAL, &do_progress, 0, 0, 0 }, + {"partial", 0, POPT_ARG_VAL, &keep_partial, 1, 0, 0 }, + {"no-partial", 0, POPT_ARG_VAL, &keep_partial, 0, 0, 0 }, + {"partial-dir", 0, POPT_ARG_STRING, &partial_dir, 0, 0, 0 }, + {"delay-updates", 0, POPT_ARG_VAL, &delay_updates, 1, 0, 0 }, + {"no-delay-updates", 0, POPT_ARG_VAL, &delay_updates, 0, 0, 0 }, + {"prune-empty-dirs",'m', POPT_ARG_VAL, &prune_empty_dirs, 1, 0, 0 }, + {"no-prune-empty-dirs",0,POPT_ARG_VAL, &prune_empty_dirs, 0, 0, 0 }, + {"no-m", 0, POPT_ARG_VAL, &prune_empty_dirs, 0, 0, 0 }, + {"log-file", 0, POPT_ARG_STRING, &logfile_name, 0, 0, 0 }, + {"log-file-format", 0, POPT_ARG_STRING, &logfile_format, 0, 0, 0 }, + {"out-format", 0, POPT_ARG_STRING, &stdout_format, 0, 0, 0 }, + {"log-format", 0, POPT_ARG_STRING, &stdout_format, 0, 0, 0 }, /* DEPRECATED */ + {"itemize-changes", 'i', POPT_ARG_NONE, 0, 'i', 0, 0 }, + {"no-itemize-changes",0, POPT_ARG_VAL, &itemize_changes, 0, 0, 0 }, + {"no-i", 0, POPT_ARG_VAL, &itemize_changes, 0, 0, 0 }, + {"bwlimit", 0, POPT_ARG_STRING, &bwlimit_arg, OPT_BWLIMIT, 0, 0 }, + {"no-bwlimit", 0, POPT_ARG_VAL, &bwlimit, 0, 0, 0 }, + {"backup", 'b', POPT_ARG_VAL, &make_backups, 1, 0, 0 }, + {"no-backup", 0, POPT_ARG_VAL, &make_backups, 0, 0, 0 }, + {"backup-dir", 0, POPT_ARG_STRING, &backup_dir, 0, 0, 0 }, + {"suffix", 0, POPT_ARG_STRING, &backup_suffix, 0, 0, 0 }, + {"list-only", 0, POPT_ARG_VAL, &list_only, 2, 0, 0 }, + {"read-batch", 0, POPT_ARG_STRING, &batch_name, OPT_READ_BATCH, 0, 0 }, + {"write-batch", 0, POPT_ARG_STRING, &batch_name, OPT_WRITE_BATCH, 0, 0 }, + {"only-write-batch", 0, POPT_ARG_STRING, &batch_name, OPT_ONLY_WRITE_BATCH, 0, 0 }, + {"files-from", 0, POPT_ARG_STRING, &files_from, 0, 0, 0 }, + {"from0", '0', POPT_ARG_VAL, &eol_nulls, 1, 0, 0}, + {"no-from0", 0, POPT_ARG_VAL, &eol_nulls, 0, 0, 0}, + {"protect-args", 's', POPT_ARG_VAL, &protect_args, 1, 0, 0}, + {"no-protect-args", 0, POPT_ARG_VAL, &protect_args, 0, 0, 0}, + {"no-s", 0, POPT_ARG_VAL, &protect_args, 0, 0, 0}, + {"numeric-ids", 0, POPT_ARG_VAL, &numeric_ids, 1, 0, 0 }, + {"no-numeric-ids", 0, POPT_ARG_VAL, &numeric_ids, 0, 0, 0 }, + {"usermap", 0, POPT_ARG_STRING, 0, OPT_USERMAP, 0, 0 }, + {"groupmap", 0, POPT_ARG_STRING, 0, OPT_GROUPMAP, 0, 0 }, + {"chown", 0, POPT_ARG_STRING, 0, OPT_CHOWN, 0, 0 }, + {"timeout", 0, POPT_ARG_INT, &io_timeout, 0, 0, 0 }, + {"no-timeout", 0, POPT_ARG_VAL, &io_timeout, 0, 0, 0 }, + {"contimeout", 0, POPT_ARG_INT, &connect_timeout, 0, 0, 0 }, + {"no-contimeout", 0, POPT_ARG_VAL, &connect_timeout, 0, 0, 0 }, + {"rsh", 'e', POPT_ARG_STRING, &shell_cmd, 0, 0, 0 }, + {"rsync-path", 0, POPT_ARG_STRING, &rsync_path, 0, 0, 0 }, + {"temp-dir", 'T', POPT_ARG_STRING, &tmpdir, 0, 0, 0 }, +#ifdef ICONV_OPTION + {"iconv", 0, POPT_ARG_STRING, &iconv_opt, 0, 0, 0 }, + {"no-iconv", 0, POPT_ARG_NONE, 0, OPT_NO_ICONV, 0, 0 }, +#endif + {"ipv4", '4', POPT_ARG_VAL, &default_af_hint, AF_INET, 0, 0 }, + {"ipv6", '6', POPT_ARG_VAL, &default_af_hint, AF_INET6, 0, 0 }, + {"8-bit-output", '8', POPT_ARG_VAL, &allow_8bit_chars, 1, 0, 0 }, + {"no-8-bit-output", 0, POPT_ARG_VAL, &allow_8bit_chars, 0, 0, 0 }, + {"no-8", 0, POPT_ARG_VAL, &allow_8bit_chars, 0, 0, 0 }, + {"qsort", 0, POPT_ARG_NONE, &use_qsort, 0, 0, 0 }, + {"address", 0, POPT_ARG_STRING, &bind_address, 0, 0, 0 }, + {"port", 0, POPT_ARG_INT, &rsync_port, 0, 0, 0 }, + {"sockopts", 0, POPT_ARG_STRING, &sockopts, 0, 0, 0 }, + {"password-file", 0, POPT_ARG_STRING, &password_file, 0, 0, 0 }, + {"blocking-io", 0, POPT_ARG_VAL, &blocking_io, 1, 0, 0 }, + {"no-blocking-io", 0, POPT_ARG_VAL, &blocking_io, 0, 0, 0 }, +#ifdef HAVE_SETVBUF + {"outbuf", 0, POPT_ARG_STRING, &outbuf_mode, 0, 0, 0 }, +#endif + {"remote-option", 'M', POPT_ARG_STRING, 0, 'M', 0, 0 }, + {"protocol", 0, POPT_ARG_INT, &protocol_version, 0, 0, 0 }, + {"checksum-seed", 0, POPT_ARG_INT, &checksum_seed, 0, 0, 0 }, + {"server", 0, POPT_ARG_NONE, 0, OPT_SERVER, 0, 0 }, + {"sender", 0, POPT_ARG_NONE, 0, OPT_SENDER, 0, 0 }, + /* All the following options switch us into daemon-mode option-parsing. */ + {"config", 0, POPT_ARG_STRING, 0, OPT_DAEMON, 0, 0 }, + {"daemon", 0, POPT_ARG_NONE, 0, OPT_DAEMON, 0, 0 }, + {"dparam", 0, POPT_ARG_STRING, 0, OPT_DAEMON, 0, 0 }, + {"detach", 0, POPT_ARG_NONE, 0, OPT_DAEMON, 0, 0 }, + {"no-detach", 0, POPT_ARG_NONE, 0, OPT_DAEMON, 0, 0 }, + {0,0,0,0, 0, 0, 0} +}; + +static void daemon_usage(enum logcode F) +{ + print_rsync_version(F); + + rprintf(F,"\n"); + rprintf(F,"Usage: rsync --daemon [OPTION]...\n"); + rprintf(F," --address=ADDRESS bind to the specified address\n"); + rprintf(F," --bwlimit=RATE limit socket I/O bandwidth\n"); + rprintf(F," --config=FILE specify alternate rsyncd.conf file\n"); + rprintf(F," -M, --dparam=OVERRIDE override global daemon config parameter\n"); + rprintf(F," --no-detach do not detach from the parent\n"); + rprintf(F," --port=PORT listen on alternate port number\n"); + rprintf(F," --log-file=FILE override the \"log file\" setting\n"); + rprintf(F," --log-file-format=FMT override the \"log format\" setting\n"); + rprintf(F," --sockopts=OPTIONS specify custom TCP options\n"); + rprintf(F," -v, --verbose increase verbosity\n"); + rprintf(F," -4, --ipv4 prefer IPv4\n"); + rprintf(F," -6, --ipv6 prefer IPv6\n"); + rprintf(F," --help show this help screen\n"); + + rprintf(F,"\n"); + rprintf(F,"If you were not trying to invoke rsync as a daemon, avoid using any of the\n"); + rprintf(F,"daemon-specific rsync options. See also the rsyncd.conf(5) man page.\n"); +} + +static struct poptOption long_daemon_options[] = { + /* longName, shortName, argInfo, argPtr, value, descrip, argDesc */ + {"address", 0, POPT_ARG_STRING, &bind_address, 0, 0, 0 }, + {"bwlimit", 0, POPT_ARG_INT, &daemon_bwlimit, 0, 0, 0 }, + {"config", 0, POPT_ARG_STRING, &config_file, 0, 0, 0 }, + {"daemon", 0, POPT_ARG_NONE, &daemon_opt, 0, 0, 0 }, + {"dparam", 'M', POPT_ARG_STRING, 0, 'M', 0, 0 }, + {"ipv4", '4', POPT_ARG_VAL, &default_af_hint, AF_INET, 0, 0 }, + {"ipv6", '6', POPT_ARG_VAL, &default_af_hint, AF_INET6, 0, 0 }, + {"detach", 0, POPT_ARG_VAL, &no_detach, 0, 0, 0 }, + {"no-detach", 0, POPT_ARG_VAL, &no_detach, 1, 0, 0 }, + {"log-file", 0, POPT_ARG_STRING, &logfile_name, 0, 0, 0 }, + {"log-file-format", 0, POPT_ARG_STRING, &logfile_format, 0, 0, 0 }, + {"port", 0, POPT_ARG_INT, &rsync_port, 0, 0, 0 }, + {"sockopts", 0, POPT_ARG_STRING, &sockopts, 0, 0, 0 }, + {"protocol", 0, POPT_ARG_INT, &protocol_version, 0, 0, 0 }, + {"server", 0, POPT_ARG_NONE, &am_server, 0, 0, 0 }, + {"temp-dir", 'T', POPT_ARG_STRING, &tmpdir, 0, 0, 0 }, + {"verbose", 'v', POPT_ARG_NONE, 0, 'v', 0, 0 }, + {"no-verbose", 0, POPT_ARG_VAL, &verbose, 0, 0, 0 }, + {"no-v", 0, POPT_ARG_VAL, &verbose, 0, 0, 0 }, + {"help", 'h', POPT_ARG_NONE, 0, 'h', 0, 0 }, + {0,0,0,0, 0, 0, 0} +}; + + +static char err_buf[200]; + + +/** + * Store the option error message, if any, so that we can log the + * connection attempt (which requires parsing the options), and then + * show the error later on. + **/ +void option_error(void) +{ + if (!err_buf[0]) { + strlcpy(err_buf, "Error parsing options: option may " + "be supported on client but not on server?\n", + sizeof err_buf); + } + + rprintf(FERROR, RSYNC_NAME ": %s", err_buf); + msleep(20); +} + + +/** + * Tweak the option table to disable all options that the rsyncd.conf + * file has told us to refuse. + **/ +static void set_refuse_options(char *bp) +{ + struct poptOption *op; + char *cp, shortname[2]; + int is_wild, found_match; + + shortname[1] = '\0'; + + while (1) { + while (*bp == ' ') bp++; + if (!*bp) + break; + if ((cp = strchr(bp, ' ')) != NULL) + *cp= '\0'; + is_wild = strpbrk(bp, "*?[") != NULL; + found_match = 0; + for (op = long_options; ; op++) { + *shortname = op->shortName; + if (!op->longName && !*shortname) + break; + if ((op->longName && wildmatch(bp, op->longName)) + || (*shortname && wildmatch(bp, shortname))) { + if (op->argInfo == POPT_ARG_VAL) + op->argInfo = POPT_ARG_NONE; + op->val = (op - long_options) + OPT_REFUSED_BASE; + found_match = 1; + /* These flags are set to let us easily check + * an implied option later in the code. */ + switch (*shortname) { + case 'r': case 'd': case 'l': case 'p': + case 't': case 'g': case 'o': case 'D': + refused_archive_part = op->val; + break; + case 'z': + refused_compress = op->val; + break; + case '\0': + if (wildmatch("delete", op->longName)) + refused_delete = op->val; + else if (wildmatch("delete-before", op->longName)) + refused_delete_before = op->val; + else if (wildmatch("delete-during", op->longName)) + refused_delete_during = op->val; + else if (wildmatch("partial", op->longName)) + refused_partial = op->val; + else if (wildmatch("progress", op->longName)) + refused_progress = op->val; + else if (wildmatch("inplace", op->longName)) + refused_inplace = op->val; + else if (wildmatch("no-iconv", op->longName)) + refused_no_iconv = op->val; + break; + } + if (!is_wild) + break; + } + } + if (!found_match) { + rprintf(FLOG, "No match for refuse-options string \"%s\"\n", + bp); + } + if (!cp) + break; + *cp = ' '; + bp = cp + 1; + } +} + + +static int count_args(const char **argv) +{ + int i = 0; + + if (argv) { + while (argv[i] != NULL) + i++; + } + + return i; +} + + +static OFF_T parse_size_arg(char **size_arg, char def_suf) +{ + int reps, mult, make_compatible = 0; + const char *arg; + OFF_T size = 1; + + for (arg = *size_arg; isDigit(arg); arg++) {} + if (*arg == '.') + for (arg++; isDigit(arg); arg++) {} + switch (*arg && *arg != '+' && *arg != '-' ? *arg++ : def_suf) { + case 'b': case 'B': + reps = 0; + break; + case 'k': case 'K': + reps = 1; + break; + case 'm': case 'M': + reps = 2; + break; + case 'g': case 'G': + reps = 3; + break; + default: + return -1; + } + if (*arg == 'b' || *arg == 'B') + mult = 1000, make_compatible = 1, arg++; + else if (!*arg || *arg == '+' || *arg == '-') + mult = 1024; + else if (strncasecmp(arg, "ib", 2) == 0) + mult = 1024, arg += 2; + else + return -1; + while (reps--) + size *= mult; + size *= atof(*size_arg); + if ((*arg == '+' || *arg == '-') && arg[1] == '1') + size += atoi(arg), make_compatible = 1, arg += 2; + if (*arg) + return -1; + if (size > 0 && make_compatible && def_suf == 'b') { + /* We convert this manually because we may need %lld precision, + * and that's not a portable sprintf() escape. */ + char buf[128], *s = buf + sizeof buf - 1; + OFF_T num = size; + *s = '\0'; + while (num) { + *--s = (char)(num % 10) + '0'; + num /= 10; + } + if (!(*size_arg = strdup(s))) + out_of_memory("parse_size_arg"); + } + return size; +} + + +static void create_refuse_error(int which) +{ + /* The "which" value is the index + OPT_REFUSED_BASE. */ + struct poptOption *op = &long_options[which - OPT_REFUSED_BASE]; + int n = snprintf(err_buf, sizeof err_buf, + "The server is configured to refuse --%s\n", + op->longName) - 1; + if (op->shortName) { + snprintf(err_buf + n, sizeof err_buf - n, + " (-%c)\n", op->shortName); + } +} + + +/** + * Process command line arguments. Called on both local and remote. + * + * @retval 1 if all options are OK; with globals set to appropriate + * values + * + * @retval 0 on error, with err_buf containing an explanation + **/ +int parse_arguments(int *argc_p, const char ***argv_p) +{ + static poptContext pc; + char *ref = lp_refuse_options(module_id); + const char *arg, **argv = *argv_p; + int argc = *argc_p; + int opt; + + if (ref && *ref) + set_refuse_options(ref); + if (am_daemon) { + set_refuse_options("log-file*"); +#ifdef ICONV_OPTION + if (!*lp_charset(module_id)) + set_refuse_options("iconv"); +#endif + } + +#ifdef ICONV_OPTION + if (!am_daemon && protect_args <= 0 && (arg = getenv("RSYNC_ICONV")) != NULL && *arg) + iconv_opt = strdup(arg); +#endif + + /* TODO: Call poptReadDefaultConfig; handle errors. */ + + /* The context leaks in case of an error, but if there's a + * problem we always exit anyhow. */ + if (pc) + poptFreeContext(pc); + pc = poptGetContext(RSYNC_NAME, argc, argv, long_options, 0); + if (!am_server) + poptReadDefaultConfig(pc, 0); + + while ((opt = poptGetNextOpt(pc)) != -1) { + /* most options are handled automatically by popt; + * only special cases are returned and listed here. */ + + switch (opt) { + case OPT_VERSION: + print_rsync_version(FINFO); + exit_cleanup(0); + + case OPT_SERVER: + if (!am_server) { + /* Disable popt aliases on the server side and + * then start parsing the options again. */ + poptFreeContext(pc); + pc = poptGetContext(RSYNC_NAME, argc, argv, + long_options, 0); + am_server = 1; + } +#ifdef ICONV_OPTION + iconv_opt = NULL; +#endif + break; + + case OPT_SENDER: + if (!am_server) { + usage(FERROR); + exit_cleanup(RERR_SYNTAX); + } + am_sender = 1; + break; + + case OPT_DAEMON: + if (am_daemon) { + strlcpy(err_buf, + "Attempt to hack rsync thwarted!\n", + sizeof err_buf); + return 0; + } +#ifdef ICONV_OPTION + iconv_opt = NULL; +#endif + protect_args = 0; + poptFreeContext(pc); + pc = poptGetContext(RSYNC_NAME, argc, argv, + long_daemon_options, 0); + while ((opt = poptGetNextOpt(pc)) != -1) { + char **cpp; + switch (opt) { + case 'h': + daemon_usage(FINFO); + exit_cleanup(0); + + case 'M': + arg = poptGetOptArg(pc); + if (!strchr(arg, '=')) { + rprintf(FERROR, + "--dparam value is missing an '=': %s\n", + arg); + goto daemon_error; + } + cpp = EXPAND_ITEM_LIST(&dparam_list, char *, 4); + *cpp = strdup(arg); + break; + + case 'v': + verbose++; + break; + + default: + rprintf(FERROR, + "rsync: %s: %s (in daemon mode)\n", + poptBadOption(pc, POPT_BADOPTION_NOALIAS), + poptStrerror(opt)); + goto daemon_error; + } + } + + if (dparam_list.count && !set_dparams(1)) + exit_cleanup(RERR_SYNTAX); + + if (tmpdir && strlen(tmpdir) >= MAXPATHLEN - 10) { + snprintf(err_buf, sizeof err_buf, + "the --temp-dir path is WAY too long.\n"); + return 0; + } + + if (!daemon_opt) { + rprintf(FERROR, "Daemon option(s) used without --daemon.\n"); + daemon_error: + rprintf(FERROR, + "(Type \"rsync --daemon --help\" for assistance with daemon mode.)\n"); + exit_cleanup(RERR_SYNTAX); + } + + *argv_p = argv = poptGetArgs(pc); + *argc_p = argc = count_args(argv); + am_starting_up = 0; + daemon_opt = 0; + am_daemon = 1; + return 1; + + case OPT_MODIFY_WINDOW: + /* The value has already been set by popt, but + * we need to remember that we're using a + * non-default setting. */ + modify_window_set = 1; + break; + + case OPT_FILTER: + parse_filter_str(&filter_list, poptGetOptArg(pc), + rule_template(0), 0); + break; + + case OPT_EXCLUDE: + parse_filter_str(&filter_list, poptGetOptArg(pc), + rule_template(0), XFLG_OLD_PREFIXES); + break; + + case OPT_INCLUDE: + parse_filter_str(&filter_list, poptGetOptArg(pc), + rule_template(FILTRULE_INCLUDE), XFLG_OLD_PREFIXES); + break; + + case OPT_EXCLUDE_FROM: + case OPT_INCLUDE_FROM: + arg = poptGetOptArg(pc); + if (sanitize_paths) + arg = sanitize_path(NULL, arg, NULL, 0, SP_DEFAULT); + if (daemon_filter_list.head) { + int rej; + char *cp = strdup(arg); + if (!cp) + out_of_memory("parse_arguments"); + if (!*cp) + rej = 1; + else { + char *dir = cp + (*cp == '/' ? module_dirlen : 0); + clean_fname(dir, CFN_COLLAPSE_DOT_DOT_DIRS); + rej = check_filter(&daemon_filter_list, FLOG, dir, 0) < 0; + } + free(cp); + if (rej) + goto options_rejected; + } + parse_filter_file(&filter_list, arg, + rule_template(opt == OPT_INCLUDE_FROM ? FILTRULE_INCLUDE : 0), + XFLG_FATAL_ERRORS | XFLG_OLD_PREFIXES); + break; + + case 'a': + if (refused_archive_part) { + create_refuse_error(refused_archive_part); + return 0; + } + if (!recurse) /* preserve recurse == 2 */ + recurse = 1; +#ifdef SUPPORT_LINKS + preserve_links = 1; +#endif + preserve_perms = 1; + preserve_times = 1; + preserve_gid = 1; + preserve_uid = 1; + preserve_devices = 1; + preserve_specials = 1; + break; + + case 'D': + preserve_devices = preserve_specials = 1; + break; + + case OPT_NO_D: + preserve_devices = preserve_specials = 0; + break; + + case 'h': + human_readable++; + break; + + case 'H': + preserve_hard_links++; + break; + + case 'i': + itemize_changes++; + break; + + case 'v': + verbose++; + break; + + case 'y': + fuzzy_basis++; + break; + + case 'q': + quiet++; + break; + + case 'x': + one_file_system++; + break; + + case 'F': + switch (++F_option_cnt) { + case 1: + parse_filter_str(&filter_list,": /.rsync-filter",rule_template(0),0); + break; + case 2: + parse_filter_str(&filter_list,"- .rsync-filter",rule_template(0),0); + break; + } + break; + + case 'P': + if (refused_partial || refused_progress) { + create_refuse_error(refused_partial + ? refused_partial : refused_progress); + return 0; + } + do_progress = 1; + keep_partial = 1; + break; + + case 'z': + do_compression++; + break; + + case 'M': + arg = poptGetOptArg(pc); + if (*arg != '-') { + snprintf(err_buf, sizeof err_buf, + "Remote option must start with a dash: %s\n", arg); + return 0; + } + if (remote_option_cnt+2 >= remote_option_alloc) { + remote_option_alloc += 16; + remote_options = realloc_array(remote_options, + const char *, remote_option_alloc); + if (!remote_options) + out_of_memory("parse_arguments"); + if (!remote_option_cnt) + remote_options[0] = "ARG0"; + } + remote_options[++remote_option_cnt] = arg; + remote_options[remote_option_cnt+1] = NULL; + break; + + case OPT_WRITE_BATCH: + /* batch_name is already set */ + write_batch = 1; + break; + + case OPT_ONLY_WRITE_BATCH: + /* batch_name is already set */ + write_batch = -1; + break; + + case OPT_READ_BATCH: + /* batch_name is already set */ + read_batch = 1; + break; + + case OPT_NO_ICONV: +#ifdef ICONV_OPTION + iconv_opt = NULL; +#endif + break; + + case OPT_MAX_SIZE: + if ((max_size = parse_size_arg(&max_size_arg, 'b')) < 0) { + snprintf(err_buf, sizeof err_buf, + "--max-size value is invalid: %s\n", + max_size_arg); + return 0; + } + break; + + case OPT_MIN_SIZE: + if ((min_size = parse_size_arg(&min_size_arg, 'b')) < 0) { + snprintf(err_buf, sizeof err_buf, + "--min-size value is invalid: %s\n", + min_size_arg); + return 0; + } + break; + + case OPT_BWLIMIT: + { + OFF_T limit = parse_size_arg(&bwlimit_arg, 'K'); + if (limit < 0) { + snprintf(err_buf, sizeof err_buf, + "--bwlimit value is invalid: %s\n", bwlimit_arg); + return 0; + } + bwlimit = (limit + 512) / 1024; + if (limit && !bwlimit) { + snprintf(err_buf, sizeof err_buf, + "--bwlimit value is too small: %s\n", bwlimit_arg); + return 0; + } + } + break; + + case OPT_APPEND: + if (am_server) + append_mode++; + else + append_mode = 1; + break; + + case OPT_LINK_DEST: +#ifdef SUPPORT_HARD_LINKS + link_dest = 1; + dest_option = "--link-dest"; + goto set_dest_dir; +#else + snprintf(err_buf, sizeof err_buf, + "hard links are not supported on this %s\n", + am_server ? "server" : "client"); + return 0; +#endif + + case OPT_COPY_DEST: + copy_dest = 1; + dest_option = "--copy-dest"; + goto set_dest_dir; + + case OPT_COMPARE_DEST: + compare_dest = 1; + dest_option = "--compare-dest"; + set_dest_dir: + if (basis_dir_cnt >= MAX_BASIS_DIRS) { + snprintf(err_buf, sizeof err_buf, + "ERROR: at most %d %s args may be specified\n", + MAX_BASIS_DIRS, dest_option); + return 0; + } + /* We defer sanitizing this arg until we know what + * our destination directory is going to be. */ + basis_dir[basis_dir_cnt++] = (char *)poptGetOptArg(pc); + break; + + case OPT_CHMOD: + arg = poptGetOptArg(pc); + if (!parse_chmod(arg, &chmod_modes)) { + snprintf(err_buf, sizeof err_buf, + "Invalid argument passed to --chmod (%s)\n", + arg); + return 0; + } + break; + + case OPT_INFO: + arg = poptGetOptArg(pc); + parse_output_words(info_words, info_levels, arg, USER_PRIORITY); + break; + + case OPT_DEBUG: + arg = poptGetOptArg(pc); + parse_output_words(debug_words, debug_levels, arg, USER_PRIORITY); + break; + + case OPT_USERMAP: + if (usermap) { + if (usermap_via_chown) { + snprintf(err_buf, sizeof err_buf, + "--usermap conflicts with prior --chown.\n"); + return 0; + } + snprintf(err_buf, sizeof err_buf, + "You can only specify --usermap once.\n"); + return 0; + } + usermap = (char *)poptGetOptArg(pc); + usermap_via_chown = False; + break; + + case OPT_GROUPMAP: + if (groupmap) { + if (groupmap_via_chown) { + snprintf(err_buf, sizeof err_buf, + "--groupmap conflicts with prior --chown.\n"); + return 0; + } + snprintf(err_buf, sizeof err_buf, + "You can only specify --groupmap once.\n"); + return 0; + } + groupmap = (char *)poptGetOptArg(pc); + groupmap_via_chown = False; + break; + + case OPT_CHOWN: { + const char *chown = poptGetOptArg(pc); + int len; + if ((arg = strchr(chown, ':')) != NULL) + len = arg++ - chown; + else + len = strlen(chown); + if (len) { + if (usermap) { + if (!usermap_via_chown) { + snprintf(err_buf, sizeof err_buf, + "--chown conflicts with prior --usermap.\n"); + return 0; + } + snprintf(err_buf, sizeof err_buf, + "You can only specify a user-affecting --chown once.\n"); + return 0; + } + if (asprintf(&usermap, "*:%.*s", len, chown) < 0) + out_of_memory("parse_arguments"); + usermap_via_chown = True; + } + if (arg && *arg) { + if (groupmap) { + if (!groupmap_via_chown) { + snprintf(err_buf, sizeof err_buf, + "--chown conflicts with prior --groupmap.\n"); + return 0; + } + snprintf(err_buf, sizeof err_buf, + "You can only specify a group-affecting --chown once.\n"); + return 0; + } + if (asprintf(&groupmap, "*:%s", arg) < 0) + out_of_memory("parse_arguments"); + groupmap_via_chown = True; + } + break; + } + + case OPT_HELP: + usage(FINFO); + exit_cleanup(0); + + case 'A': +#ifdef SUPPORT_ACLS + preserve_acls = 1; + preserve_perms = 1; + break; +#else + /* FIXME: this should probably be ignored with a + * warning and then countermeasures taken to + * restrict group and other access in the presence + * of any more restrictive ACLs, but this is safe + * for now */ + snprintf(err_buf,sizeof(err_buf), + "ACLs are not supported on this %s\n", + am_server ? "server" : "client"); + return 0; +#endif + + case 'X': +#ifdef SUPPORT_XATTRS + preserve_xattrs++; + break; +#else + snprintf(err_buf,sizeof(err_buf), + "extended attributes are not supported on this %s\n", + am_server ? "server" : "client"); + return 0; +#endif + + default: + /* A large opt value means that set_refuse_options() + * turned this option off. */ + if (opt >= OPT_REFUSED_BASE) { + create_refuse_error(opt); + return 0; + } + snprintf(err_buf, sizeof err_buf, "%s%s: %s\n", + am_server ? "on remote machine: " : "", + poptBadOption(pc, POPT_BADOPTION_NOALIAS), + poptStrerror(opt)); + return 0; + } + } + + if (protect_args < 0) { + if (am_server) + protect_args = 0; + else if ((arg = getenv("RSYNC_PROTECT_ARGS")) != NULL && *arg) + protect_args = atoi(arg) ? 1 : 0; + else { +#ifdef RSYNC_USE_PROTECTED_ARGS + protect_args = 1; +#else + protect_args = 0; +#endif + } + } + + if (human_readable > 1 && argc == 2 && !am_server) { + /* Allow the old meaning of 'h' (--help) on its own. */ + usage(FINFO); + exit_cleanup(0); + } + + if (do_compression || def_compress_level != NOT_SPECIFIED) { + if (def_compress_level == NOT_SPECIFIED) + def_compress_level = Z_DEFAULT_COMPRESSION; + else if (def_compress_level < Z_DEFAULT_COMPRESSION || def_compress_level > Z_BEST_COMPRESSION) { + snprintf(err_buf, sizeof err_buf, "--compress-level value is invalid: %d\n", + def_compress_level); + return 0; + } else if (def_compress_level == Z_NO_COMPRESSION) + do_compression = 0; + else if (!do_compression) + do_compression = 1; + if (do_compression && refused_compress) { + create_refuse_error(refused_compress); + return 0; + } +#ifdef EXTERNAL_ZLIB + if (do_compression == 1) { + snprintf(err_buf, sizeof err_buf, + "This rsync lacks old-style --compress due to its external zlib. Try -zz.\n"); + if (am_server) + return 0; + fprintf(stderr, "%s" "Continuing without compression.\n\n", err_buf); + do_compression = 0; + } +#endif + } + +#ifdef HAVE_SETVBUF + if (outbuf_mode && !am_server) { + int mode = *(uchar *)outbuf_mode; + if (islower(mode)) + mode = toupper(mode); + fflush(stdout); /* Just in case... */ + switch (mode) { + case 'N': /* None */ + case 'U': /* Unbuffered */ + mode = _IONBF; + break; + case 'L': /* Line */ + mode = _IOLBF; + break; + case 'B': /* Block */ + case 'F': /* Full */ + mode = _IOFBF; + break; + default: + snprintf(err_buf, sizeof err_buf, + "Invalid --outbuf setting -- specify N, L, or B.\n"); + return 0; + } + setvbuf(stdout, (char *)NULL, mode, 0); + } + + if (msgs2stderr) { + /* Make stderr line buffered for better sharing of the stream. */ + fflush(stderr); /* Just in case... */ + setvbuf(stderr, (char *)NULL, _IOLBF, 0); + } +#endif + + set_output_verbosity(verbose, DEFAULT_PRIORITY); + + if (do_stats) { + parse_output_words(info_words, info_levels, + verbose > 1 ? "stats3" : "stats2", DEFAULT_PRIORITY); + } + +#ifdef ICONV_OPTION + if (iconv_opt && protect_args != 2) { + if (!am_server && strcmp(iconv_opt, "-") == 0) + iconv_opt = NULL; + else + need_unsorted_flist = 1; + } + if (refused_no_iconv && !iconv_opt) { + create_refuse_error(refused_no_iconv); + return 0; + } +#endif + + if (fuzzy_basis > 1) + fuzzy_basis = basis_dir_cnt + 1; + + if (protect_args == 1 && am_server) + return 1; + + *argv_p = argv = poptGetArgs(pc); + *argc_p = argc = count_args(argv); + +#ifndef SUPPORT_LINKS + if (preserve_links && !am_sender) { + snprintf(err_buf, sizeof err_buf, + "symlinks are not supported on this %s\n", + am_server ? "server" : "client"); + return 0; + } +#endif + +#ifndef SUPPORT_HARD_LINKS + if (preserve_hard_links) { + snprintf(err_buf, sizeof err_buf, + "hard links are not supported on this %s\n", + am_server ? "server" : "client"); + return 0; + } +#endif + +#ifdef SUPPORT_XATTRS + if (am_root < 0 && preserve_xattrs > 1) { + snprintf(err_buf, sizeof err_buf, + "--fake-super conflicts with -XX\n"); + return 0; + } +#else + if (am_root < 0) { + snprintf(err_buf, sizeof err_buf, + "--fake-super requires an rsync with extended attributes enabled\n"); + return 0; + } +#endif + + if (block_size > MAX_BLOCK_SIZE) { + snprintf(err_buf, sizeof err_buf, + "--block-size=%lu is too large (max: %u)\n", block_size, MAX_BLOCK_SIZE); + return 0; + } + + if (write_batch && read_batch) { + snprintf(err_buf, sizeof err_buf, + "--write-batch and --read-batch can not be used together\n"); + return 0; + } + if (write_batch > 0 || read_batch) { + if (am_server) { + rprintf(FINFO, + "ignoring --%s-batch option sent to server\n", + write_batch ? "write" : "read"); + /* We don't actually exit_cleanup(), so that we can + * still service older version clients that still send + * batch args to server. */ + read_batch = write_batch = 0; + batch_name = NULL; + } else if (dry_run) + write_batch = 0; + } else if (write_batch < 0 && dry_run) + write_batch = 0; + if (read_batch && files_from) { + snprintf(err_buf, sizeof err_buf, + "--read-batch cannot be used with --files-from\n"); + return 0; + } + if (read_batch && remove_source_files) { + snprintf(err_buf, sizeof err_buf, + "--read-batch cannot be used with --remove-%s-files\n", + remove_source_files == 1 ? "source" : "sent"); + return 0; + } + if (batch_name && strlen(batch_name) > MAX_BATCH_NAME_LEN) { + snprintf(err_buf, sizeof err_buf, + "the batch-file name must be %d characters or less.\n", + MAX_BATCH_NAME_LEN); + return 0; + } + + if (tmpdir && strlen(tmpdir) >= MAXPATHLEN - 10) { + snprintf(err_buf, sizeof err_buf, + "the --temp-dir path is WAY too long.\n"); + return 0; + } + + if (max_delete < 0 && max_delete != INT_MIN) { + /* Negative numbers are treated as "no deletions". */ + max_delete = 0; + } + + if (compare_dest + copy_dest + link_dest > 1) { + snprintf(err_buf, sizeof err_buf, + "You may not mix --compare-dest, --copy-dest, and --link-dest.\n"); + return 0; + } + + if (files_from) { + if (recurse == 1) /* preserve recurse == 2 */ + recurse = 0; + if (xfer_dirs < 0) + xfer_dirs = 1; + } + + if (argc < 2 && !read_batch && !am_server) + list_only |= 1; + + if (xfer_dirs >= 4) { + parse_filter_str(&filter_list, "- /*/*", rule_template(0), 0); + recurse = xfer_dirs = 1; + } else if (recurse) + xfer_dirs = 1; + else if (xfer_dirs < 0) + xfer_dirs = list_only ? 1 : 0; + + if (relative_paths < 0) + relative_paths = files_from? 1 : 0; + if (!relative_paths) + implied_dirs = 0; + + if (delete_before + !!delete_during + delete_after > 1) { + snprintf(err_buf, sizeof err_buf, + "You may not combine multiple --delete-WHEN options.\n"); + return 0; + } + if (delete_before || delete_during || delete_after) + delete_mode = 1; + else if (delete_mode || delete_excluded) { + /* Only choose now between before & during if one is refused. */ + if (refused_delete_before) { + if (!refused_delete_during) + delete_during = 1; + else { + create_refuse_error(refused_delete_before); + return 0; + } + } else if (refused_delete_during) + delete_before = 1; + delete_mode = 1; + } + if (!xfer_dirs && delete_mode) { + snprintf(err_buf, sizeof err_buf, + "--delete does not work without --recursive (-r) or --dirs (-d).\n"); + return 0; + } + + if (missing_args == 3) /* simplify if both options were specified */ + missing_args = 2; + if (refused_delete && (delete_mode || missing_args == 2)) { + create_refuse_error(refused_delete); + return 0; + } + + if (remove_source_files) { + /* We only want to infer this refusal of --remove-source-files + * via the refusal of "delete", not any of the "delete-FOO" + * options. */ + if (refused_delete && am_sender) { + create_refuse_error(refused_delete); + return 0; + } + need_messages_from_generator = 1; + } + + if (munge_symlinks && !am_daemon) { + STRUCT_STAT st; + char prefix[SYMLINK_PREFIX_LEN]; /* NOT +1 ! */ + strlcpy(prefix, SYMLINK_PREFIX, sizeof prefix); /* trim the trailing slash */ + if (do_stat(prefix, &st) == 0 && S_ISDIR(st.st_mode)) { + rprintf(FERROR, "Symlink munging is unsafe when a %s directory exists.\n", + prefix); + exit_cleanup(RERR_UNSUPPORTED); + } + } + + if (sanitize_paths) { + int i; + for (i = argc; i-- > 0; ) + argv[i] = sanitize_path(NULL, argv[i], "", 0, SP_KEEP_DOT_DIRS); + if (tmpdir) + tmpdir = sanitize_path(NULL, tmpdir, NULL, 0, SP_DEFAULT); + if (backup_dir) + backup_dir = sanitize_path(NULL, backup_dir, NULL, 0, SP_DEFAULT); + } + if (daemon_filter_list.head && !am_sender) { + filter_rule_list *elp = &daemon_filter_list; + if (tmpdir) { + char *dir; + if (!*tmpdir) + goto options_rejected; + dir = tmpdir + (*tmpdir == '/' ? module_dirlen : 0); + clean_fname(dir, CFN_COLLAPSE_DOT_DOT_DIRS); + if (check_filter(elp, FLOG, dir, 1) < 0) + goto options_rejected; + } + if (backup_dir) { + char *dir; + if (!*backup_dir) + goto options_rejected; + dir = backup_dir + (*backup_dir == '/' ? module_dirlen : 0); + clean_fname(dir, CFN_COLLAPSE_DOT_DOT_DIRS); + if (check_filter(elp, FLOG, dir, 1) < 0) + goto options_rejected; + } + } + + if (!backup_suffix) + backup_suffix = backup_dir ? "" : BACKUP_SUFFIX; + backup_suffix_len = strlen(backup_suffix); + if (strchr(backup_suffix, '/') != NULL) { + snprintf(err_buf, sizeof err_buf, + "--suffix cannot contain slashes: %s\n", + backup_suffix); + return 0; + } + if (backup_dir) { + size_t len; + while (*backup_dir == '.' && backup_dir[1] == '/') + backup_dir += 2; + if (*backup_dir == '.' && backup_dir[1] == '\0') + backup_dir++; + len = strlcpy(backup_dir_buf, backup_dir, sizeof backup_dir_buf); + if (len > sizeof backup_dir_buf - 128) { + snprintf(err_buf, sizeof err_buf, + "the --backup-dir path is WAY too long.\n"); + return 0; + } + backup_dir_len = (int)len; + if (!backup_dir_len) { + backup_dir_len = -1; + backup_dir = NULL; + } else if (backup_dir_buf[backup_dir_len - 1] != '/') { + backup_dir_buf[backup_dir_len++] = '/'; + backup_dir_buf[backup_dir_len] = '\0'; + } + backup_dir_remainder = sizeof backup_dir_buf - backup_dir_len; + } + if (backup_dir) { + /* No need for a suffix or a protect rule. */ + } else if (!backup_suffix_len && (!am_server || !am_sender)) { + snprintf(err_buf, sizeof err_buf, + "--suffix cannot be empty %s\n", backup_dir_len < 0 + ? "when --backup-dir is the same as the dest dir" + : "without a --backup-dir"); + return 0; + } else if (make_backups && delete_mode && !delete_excluded && !am_server) { + snprintf(backup_dir_buf, sizeof backup_dir_buf, + "P *%s", backup_suffix); + parse_filter_str(&filter_list, backup_dir_buf, rule_template(0), 0); + } + + if (preserve_times) { + preserve_times = PRESERVE_FILE_TIMES; + if (!omit_dir_times) + preserve_times |= PRESERVE_DIR_TIMES; +#ifdef CAN_SET_SYMLINK_TIMES + if (!omit_link_times) + preserve_times |= PRESERVE_LINK_TIMES; +#endif + } + + if (make_backups && !backup_dir) { + omit_dir_times = 0; /* Implied, so avoid -O to sender. */ + preserve_times &= ~PRESERVE_DIR_TIMES; + } + + if (stdout_format) { + if (am_server && log_format_has(stdout_format, 'I')) + stdout_format_has_i = 2; + else if (log_format_has(stdout_format, 'i')) + stdout_format_has_i = itemize_changes | 1; + if (!log_format_has(stdout_format, 'b') + && !log_format_has(stdout_format, 'c') + && !log_format_has(stdout_format, 'C')) + log_before_transfer = !am_server; + } else if (itemize_changes) { + stdout_format = "%i %n%L"; + stdout_format_has_i = itemize_changes; + log_before_transfer = !am_server; + } + + if (do_progress && !am_server) { + if (!log_before_transfer && INFO_EQ(NAME, 0)) + parse_output_words(info_words, info_levels, "name", DEFAULT_PRIORITY); + parse_output_words(info_words, info_levels, "flist2,progress", DEFAULT_PRIORITY); + } + + if (dry_run) + do_xfers = 0; + + set_io_timeout(io_timeout); + + if (INFO_GTE(NAME, 1) && !stdout_format) { + stdout_format = "%n%L"; + log_before_transfer = !am_server; + } + if (stdout_format_has_i || log_format_has(stdout_format, 'o')) + stdout_format_has_o_or_i = 1; + + if (logfile_name && !am_daemon) { + if (!logfile_format) { + logfile_format = "%i %n%L"; + logfile_format_has_i = logfile_format_has_o_or_i = 1; + } else { + if (log_format_has(logfile_format, 'i')) + logfile_format_has_i = 1; + if (logfile_format_has_i || log_format_has(logfile_format, 'o')) + logfile_format_has_o_or_i = 1; + } + log_init(0); + } else if (!am_daemon) + logfile_format = NULL; + + if (daemon_bwlimit && (!bwlimit || bwlimit > daemon_bwlimit)) + bwlimit = daemon_bwlimit; + if (bwlimit) { + bwlimit_writemax = (size_t)bwlimit * 128; + if (bwlimit_writemax < 512) + bwlimit_writemax = 512; + } + + if (sparse_files && inplace) { + /* Note: we don't check for this below, because --append is + * OK with --sparse (as long as redos are handled right). */ + snprintf(err_buf, sizeof err_buf, + "--sparse cannot be used with --inplace\n"); + return 0; + } + + if (append_mode) { + if (whole_file > 0) { + snprintf(err_buf, sizeof err_buf, + "--append cannot be used with --whole-file\n"); + return 0; + } + if (refused_inplace) { + create_refuse_error(refused_inplace); + return 0; + } + inplace = 1; + } + + if (delay_updates && !partial_dir) + partial_dir = tmp_partialdir; + + if (inplace) { +#ifdef HAVE_FTRUNCATE + if (partial_dir) { + snprintf(err_buf, sizeof err_buf, + "--%s cannot be used with --%s\n", + append_mode ? "append" : "inplace", + delay_updates ? "delay-updates" : "partial-dir"); + return 0; + } + /* --inplace implies --partial for refusal purposes, but we + * clear the keep_partial flag for internal logic purposes. */ + if (refused_partial) { + create_refuse_error(refused_partial); + return 0; + } + keep_partial = 0; +#else + snprintf(err_buf, sizeof err_buf, + "--%s is not supported on this %s\n", + append_mode ? "append" : "inplace", + am_server ? "server" : "client"); + return 0; +#endif + } else { + if (keep_partial && !partial_dir && !am_server) { + if ((arg = getenv("RSYNC_PARTIAL_DIR")) != NULL && *arg) + partial_dir = strdup(arg); + } + if (partial_dir) { + if (*partial_dir) + clean_fname(partial_dir, CFN_COLLAPSE_DOT_DOT_DIRS); + if (!*partial_dir || strcmp(partial_dir, ".") == 0) + partial_dir = NULL; + if (!partial_dir && refused_partial) { + create_refuse_error(refused_partial); + return 0; + } + keep_partial = 1; + } + } + + if (files_from) { + char *h, *p; + int q; + if (argc > 2 || (!am_daemon && !am_server && argc == 1)) { + usage(FERROR); + exit_cleanup(RERR_SYNTAX); + } + if (strcmp(files_from, "-") == 0) { + filesfrom_fd = 0; + if (am_server) + filesfrom_host = ""; /* reading from socket */ + } else if ((p = check_for_hostspec(files_from, &h, &q)) != 0) { + if (am_server) { + snprintf(err_buf, sizeof err_buf, + "The --files-from sent to the server cannot specify a host.\n"); + return 0; + } + files_from = p; + filesfrom_host = h; + if (strcmp(files_from, "-") == 0) { + snprintf(err_buf, sizeof err_buf, + "Invalid --files-from remote filename\n"); + return 0; + } + } else { + if (sanitize_paths) + files_from = sanitize_path(NULL, files_from, NULL, 0, SP_DEFAULT); + if (daemon_filter_list.head) { + char *dir; + if (!*files_from) + goto options_rejected; + dir = files_from + (*files_from == '/' ? module_dirlen : 0); + clean_fname(dir, CFN_COLLAPSE_DOT_DOT_DIRS); + if (check_filter(&daemon_filter_list, FLOG, dir, 0) < 0) + goto options_rejected; + } + filesfrom_fd = open(files_from, O_RDONLY|O_BINARY); + if (filesfrom_fd < 0) { + snprintf(err_buf, sizeof err_buf, + "failed to open files-from file %s: %s\n", + files_from, strerror(errno)); + return 0; + } + } + } + + am_starting_up = 0; + + return 1; + + options_rejected: + snprintf(err_buf, sizeof err_buf, + "Your options have been rejected by the server.\n"); + return 0; +} + + +/** + * Construct a filtered list of options to pass through from the + * client to the server. + * + * This involves setting options that will tell the server how to + * behave, and also filtering out options that are processed only + * locally. + **/ +void server_options(char **args, int *argc_p) +{ + static char argstr[64]; + int ac = *argc_p; + uchar where; + char *arg; + int i, x; + + /* This should always remain first on the server's command-line. */ + args[ac++] = "--server"; + + if (daemon_over_rsh > 0) { + args[ac++] = "--daemon"; + *argc_p = ac; + /* if we're passing --daemon, we're done */ + return; + } + + if (!am_sender) + args[ac++] = "--sender"; + + x = 1; + argstr[0] = '-'; + + if (protect_args) + argstr[x++] = 's'; + + for (i = 0; i < verbose; i++) + argstr[x++] = 'v'; + + /* the -q option is intentionally left out */ + if (make_backups) + argstr[x++] = 'b'; + if (update_only) + argstr[x++] = 'u'; + if (!do_xfers) /* Note: NOT "dry_run"! */ + argstr[x++] = 'n'; + if (preserve_links) + argstr[x++] = 'l'; + if ((xfer_dirs >= 2 && xfer_dirs < 4) + || (xfer_dirs && !recurse && (list_only || (delete_mode && am_sender)))) + argstr[x++] = 'd'; + if (am_sender) { + if (keep_dirlinks) + argstr[x++] = 'K'; + if (prune_empty_dirs) + argstr[x++] = 'm'; + if (omit_dir_times) + argstr[x++] = 'O'; + if (omit_link_times) + argstr[x++] = 'J'; + if (fuzzy_basis) { + argstr[x++] = 'y'; + if (fuzzy_basis > 1) + argstr[x++] = 'y'; + } + } else { + if (copy_links) + argstr[x++] = 'L'; + if (copy_dirlinks) + argstr[x++] = 'k'; + } + + if (whole_file > 0) + argstr[x++] = 'W'; + /* We don't need to send --no-whole-file, because it's the + * default for remote transfers, and in any case old versions + * of rsync will not understand it. */ + + if (preserve_hard_links) { + argstr[x++] = 'H'; + if (preserve_hard_links > 1) + argstr[x++] = 'H'; + } + if (preserve_uid) + argstr[x++] = 'o'; + if (preserve_gid) + argstr[x++] = 'g'; + if (preserve_devices) /* ignore preserve_specials here */ + argstr[x++] = 'D'; + if (preserve_times) + argstr[x++] = 't'; + if (preserve_perms) + argstr[x++] = 'p'; + else if (preserve_executability && am_sender) + argstr[x++] = 'E'; +#ifdef SUPPORT_ACLS + if (preserve_acls) + argstr[x++] = 'A'; +#endif +#ifdef SUPPORT_XATTRS + if (preserve_xattrs) { + argstr[x++] = 'X'; + if (preserve_xattrs > 1) + argstr[x++] = 'X'; + } +#endif + if (recurse) + argstr[x++] = 'r'; + if (always_checksum) + argstr[x++] = 'c'; + if (cvs_exclude) + argstr[x++] = 'C'; + if (ignore_times) + argstr[x++] = 'I'; + if (relative_paths) + argstr[x++] = 'R'; + if (one_file_system) { + argstr[x++] = 'x'; + if (one_file_system > 1) + argstr[x++] = 'x'; + } + if (sparse_files) + argstr[x++] = 'S'; + if (do_compression == 1) + argstr[x++] = 'z'; + + set_allow_inc_recurse(); + + /* We don't really know the actual protocol_version at this point, + * but checking the pre-negotiated value allows the user to use a + * --protocol=29 override to avoid the use of this -eFLAGS opt. */ + if (protocol_version >= 30) { + /* We make use of the -e option to let the server know about + * any pre-release protocol version && some behavior flags. */ + argstr[x++] = 'e'; +#if SUBPROTOCOL_VERSION != 0 + if (protocol_version == PROTOCOL_VERSION) { + x += snprintf(argstr+x, sizeof argstr - x, + "%d.%d", + PROTOCOL_VERSION, SUBPROTOCOL_VERSION); + } else +#endif + argstr[x++] = '.'; + if (allow_inc_recurse) + argstr[x++] = 'i'; +#ifdef CAN_SET_SYMLINK_TIMES + argstr[x++] = 'L'; /* symlink time-setting support */ +#endif +#ifdef ICONV_OPTION + argstr[x++] = 's'; /* symlink iconv translation support */ +#endif + argstr[x++] = 'f'; /* flist I/O-error safety support */ + argstr[x++] = 'x'; /* xattr hardlink optimization not desired */ + } + + if (x >= (int)sizeof argstr) { /* Not possible... */ + rprintf(FERROR, "argstr overflow in server_options().\n"); + exit_cleanup(RERR_MALLOC); + } + + argstr[x] = '\0'; + + if (x > 1) + args[ac++] = argstr; + +#ifdef ICONV_OPTION + if (iconv_opt) { + char *set = strchr(iconv_opt, ','); + if (set) + set++; + else + set = iconv_opt; + if (asprintf(&arg, "--iconv=%s", set) < 0) + goto oom; + args[ac++] = arg; + } +#endif + + if (protect_args && !local_server) /* unprotected args stop here */ + args[ac++] = NULL; + + if (list_only > 1) + args[ac++] = "--list-only"; + + /* This makes sure that the remote rsync can handle deleting with -d + * sans -r because the --no-r option was added at the same time. */ + if (xfer_dirs && !recurse && delete_mode && am_sender) + args[ac++] = "--no-r"; + + if (do_compression && def_compress_level != Z_DEFAULT_COMPRESSION) { + if (asprintf(&arg, "--compress-level=%d", def_compress_level) < 0) + goto oom; + args[ac++] = arg; + } + + if (preserve_devices) { + /* Note: sending "--devices" would not be backward-compatible. */ + if (!preserve_specials) + args[ac++] = "--no-specials"; /* -D is already set. */ + } else if (preserve_specials) + args[ac++] = "--specials"; + + /* The server side doesn't use our log-format, but in certain + * circumstances they need to know a little about the option. */ + if (stdout_format && am_sender) { + /* Use --log-format, not --out-format, for compatibility. */ + if (stdout_format_has_i > 1) + args[ac++] = "--log-format=%i%I"; + else if (stdout_format_has_i) + args[ac++] = "--log-format=%i"; + else if (stdout_format_has_o_or_i) + args[ac++] = "--log-format=%o"; + else if (!verbose) + args[ac++] = "--log-format=X"; + } + + if (block_size) { + if (asprintf(&arg, "-B%lu", block_size) < 0) + goto oom; + args[ac++] = arg; + } + + if (io_timeout) { + if (asprintf(&arg, "--timeout=%d", io_timeout) < 0) + goto oom; + args[ac++] = arg; + } + + if (bwlimit) { + if (asprintf(&arg, "--bwlimit=%d", bwlimit) < 0) + goto oom; + args[ac++] = arg; + } + + if (backup_dir) { + args[ac++] = "--backup-dir"; + args[ac++] = backup_dir; + } + + /* Only send --suffix if it specifies a non-default value. */ + if (strcmp(backup_suffix, backup_dir ? "" : BACKUP_SUFFIX) != 0) { + /* We use the following syntax to avoid weirdness with '~'. */ + if (asprintf(&arg, "--suffix=%s", backup_suffix) < 0) + goto oom; + args[ac++] = arg; + } + + if (am_sender) { + if (max_delete > 0) { + if (asprintf(&arg, "--max-delete=%d", max_delete) < 0) + goto oom; + args[ac++] = arg; + } else if (max_delete == 0) + args[ac++] = "--max-delete=-1"; + if (min_size >= 0) { + args[ac++] = "--min-size"; + args[ac++] = min_size_arg; + } + if (max_size >= 0) { + args[ac++] = "--max-size"; + args[ac++] = max_size_arg; + } + if (delete_before) + args[ac++] = "--delete-before"; + else if (delete_during == 2) + args[ac++] = "--delete-delay"; + else if (delete_during) + args[ac++] = "--delete-during"; + else if (delete_after) + args[ac++] = "--delete-after"; + else if (delete_mode && !delete_excluded) + args[ac++] = "--delete"; + if (delete_excluded) + args[ac++] = "--delete-excluded"; + if (force_delete) + args[ac++] = "--force"; + if (write_batch < 0) + args[ac++] = "--only-write-batch=X"; + if (am_root > 1) + args[ac++] = "--super"; + if (size_only) + args[ac++] = "--size-only"; + if (do_stats) + args[ac++] = "--stats"; + } else { + if (skip_compress) { + if (asprintf(&arg, "--skip-compress=%s", skip_compress) < 0) + goto oom; + args[ac++] = arg; + } + } + + /* --delete-missing-args needs the cooperation of both sides, but + * the sender can handle --ignore-missing-args by itself. */ + if (missing_args == 2) + args[ac++] = "--delete-missing-args"; + else if (missing_args == 1 && !am_sender) + args[ac++] = "--ignore-missing-args"; + + if (modify_window_set) { + if (asprintf(&arg, "--modify-window=%d", modify_window) < 0) + goto oom; + args[ac++] = arg; + } + + if (checksum_seed) { + if (asprintf(&arg, "--checksum-seed=%d", checksum_seed) < 0) + goto oom; + args[ac++] = arg; + } + + if (partial_dir && am_sender) { + if (partial_dir != tmp_partialdir) { + args[ac++] = "--partial-dir"; + args[ac++] = partial_dir; + } + if (delay_updates) + args[ac++] = "--delay-updates"; + } else if (keep_partial && am_sender) + args[ac++] = "--partial"; + + if (ignore_errors) + args[ac++] = "--ignore-errors"; + + if (copy_unsafe_links) + args[ac++] = "--copy-unsafe-links"; + + if (safe_symlinks) + args[ac++] = "--safe-links"; + + if (numeric_ids) + args[ac++] = "--numeric-ids"; + + if (use_qsort) + args[ac++] = "--use-qsort"; + + if (am_sender) { + if (usermap) { + if (asprintf(&arg, "--usermap=%s", usermap) < 0) + goto oom; + args[ac++] = arg; + } + + if (groupmap) { + if (asprintf(&arg, "--groupmap=%s", groupmap) < 0) + goto oom; + args[ac++] = arg; + } + + if (ignore_existing) + args[ac++] = "--ignore-existing"; + + /* Backward compatibility: send --existing, not --ignore-non-existing. */ + if (ignore_non_existing) + args[ac++] = "--existing"; + + if (tmpdir) { + args[ac++] = "--temp-dir"; + args[ac++] = tmpdir; + } + + if (basis_dir[0]) { + /* the server only needs this option if it is not the sender, + * and it may be an older version that doesn't know this + * option, so don't send it if client is the sender. + */ + for (i = 0; i < basis_dir_cnt; i++) { + args[ac++] = dest_option; + args[ac++] = basis_dir[i]; + } + } + } + + /* What flags do we need to send to the other side? */ + where = (am_server ? W_CLI : W_SRV) | (am_sender ? W_REC : W_SND); + arg = make_output_option(info_words, info_levels, where); + if (arg) + args[ac++] = arg; + + arg = make_output_option(debug_words, debug_levels, where); + if (arg) + args[ac++] = arg; + + if (append_mode) { + if (append_mode > 1) + args[ac++] = "--append"; + args[ac++] = "--append"; + } else if (inplace) + args[ac++] = "--inplace"; + + if (files_from && (!am_sender || filesfrom_host)) { + if (filesfrom_host) { + args[ac++] = "--files-from"; + args[ac++] = files_from; + if (eol_nulls) + args[ac++] = "--from0"; + } else { + args[ac++] = "--files-from=-"; + args[ac++] = "--from0"; + } + if (!relative_paths) + args[ac++] = "--no-relative"; + } + /* It's OK that this checks the upper-bound of the protocol_version. */ + if (relative_paths && !implied_dirs && (!am_sender || protocol_version >= 30)) + args[ac++] = "--no-implied-dirs"; + + if (remove_source_files == 1) + args[ac++] = "--remove-source-files"; + else if (remove_source_files) + args[ac++] = "--remove-sent-files"; + + if (preallocate_files && am_sender) + args[ac++] = "--preallocate"; + + if (ac > MAX_SERVER_ARGS) { /* Not possible... */ + rprintf(FERROR, "argc overflow in server_options().\n"); + exit_cleanup(RERR_MALLOC); + } + + if (do_compression > 1) + args[ac++] = "--new-compress"; + + if (remote_option_cnt) { + int j; + if (ac + remote_option_cnt > MAX_SERVER_ARGS) { + rprintf(FERROR, "too many remote options specified.\n"); + exit_cleanup(RERR_SYNTAX); + } + for (j = 1; j <= remote_option_cnt; j++) + args[ac++] = (char*)remote_options[j]; + } + + *argc_p = ac; + return; + + oom: + out_of_memory("server_options"); +} + +/* If str points to a valid hostspec, return allocated memory containing the + * [USER@]HOST part of the string, and set the path_start_ptr to the part of + * the string after the host part. Otherwise, return NULL. If port_ptr is + * non-NULL, we must be parsing an rsync:// URL hostname, and we will set + * *port_ptr if a port number is found. Note that IPv6 IPs will have their + * (required for parsing) [ and ] chars elided from the returned string. */ +static char *parse_hostspec(char *str, char **path_start_ptr, int *port_ptr) +{ + char *s, *host_start = str; + int hostlen = 0, userlen = 0; + char *ret; + + for (s = str; ; s++) { + if (!*s) { + /* It is only OK if we run out of string with rsync:// */ + if (!port_ptr) + return NULL; + if (!hostlen) + hostlen = s - host_start; + break; + } + if (*s == ':' || *s == '/') { + if (!hostlen) + hostlen = s - host_start; + if (*s++ == '/') { + if (!port_ptr) + return NULL; + } else if (port_ptr) { + *port_ptr = atoi(s); + while (isDigit(s)) s++; + if (*s && *s++ != '/') + return NULL; + } + break; + } + if (*s == '@') { + userlen = s - str + 1; + host_start = s + 1; + } else if (*s == '[') { + if (s != host_start++) + return NULL; + while (*s && *s != ']' && *s != '/') s++; /*SHARED ITERATOR*/ + hostlen = s - host_start; + if (*s != ']' || (s[1] && s[1] != '/' && s[1] != ':') || !hostlen) + return NULL; + } + } + + *path_start_ptr = s; + ret = new_array(char, userlen + hostlen + 1); + if (userlen) + strlcpy(ret, str, userlen + 1); + strlcpy(ret + userlen, host_start, hostlen + 1); + return ret; +} + +/* Look for a HOST specfication of the form "HOST:PATH", "HOST::PATH", or + * "rsync://HOST:PORT/PATH". If found, *host_ptr will be set to some allocated + * memory with the HOST. If a daemon-accessing spec was specified, the value + * of *port_ptr will contain a non-0 port number, otherwise it will be set to + * 0. The return value is a pointer to the PATH. Note that the HOST spec can + * be an IPv6 literal address enclosed in '[' and ']' (such as "[::1]" or + * "[::ffff:127.0.0.1]") which is returned without the '[' and ']'. */ +char *check_for_hostspec(char *s, char **host_ptr, int *port_ptr) +{ + char *path; + + if (port_ptr && strncasecmp(URL_PREFIX, s, strlen(URL_PREFIX)) == 0) { + *host_ptr = parse_hostspec(s + strlen(URL_PREFIX), &path, port_ptr); + if (*host_ptr) { + if (!*port_ptr) + *port_ptr = RSYNC_PORT; + return path; + } + } + + *host_ptr = parse_hostspec(s, &path, NULL); + if (!*host_ptr) + return NULL; + + if (*path == ':') { + if (port_ptr && !*port_ptr) + *port_ptr = RSYNC_PORT; + return path + 1; + } + if (port_ptr) + *port_ptr = 0; + + return path; +} diff --git a/rsync/params.c b/rsync/params.c new file mode 100644 index 0000000..0fbd986 --- /dev/null +++ b/rsync/params.c @@ -0,0 +1,666 @@ +/* This modules is based on the params.c module from Samba, written by Karl Auer + and much modifed by Christopher Hertel. */ + +/* + * 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, visit the http://fsf.org website. + */ + +/* -------------------------------------------------------------------------- ** + * + * Module name: params + * + * -------------------------------------------------------------------------- ** + * + * This module performs lexical analysis and initial parsing of a + * Windows-like parameter file. It recognizes and handles four token + * types: section-name, parameter-name, parameter-value, and + * end-of-file. Comments and line continuation are handled + * internally. + * + * The entry point to the module is function pm_process(). This + * function opens the source file, calls the Parse() function to parse + * the input, and then closes the file when either the EOF is reached + * or a fatal error is encountered. + * + * A sample parameter file might look like this: + * + * [section one] + * parameter one = value string + * parameter two = another value + * [section two] + * new parameter = some value or t'other + * + * The parameter file is divided into sections by section headers: + * section names enclosed in square brackets (eg. [section one]). + * Each section contains parameter lines, each of which consist of a + * parameter name and value delimited by an equal sign. Roughly, the + * syntax is: + * + * :== {
} EOF + * + *
:==
{ } + * + *
:== '[' NAME ']' + * + * :== NAME '=' VALUE '\n' + * + * Blank lines and comment lines are ignored. Comment lines are lines + * beginning with either a semicolon (';') or a pound sign ('#'). + * + * All whitespace in section names and parameter names is compressed + * to single spaces. Leading and trailing whitespace is stipped from + * both names and values. + * + * Only the first equals sign in a parameter line is significant. + * Parameter values may contain equals signs, square brackets and + * semicolons. Internal whitespace is retained in parameter values, + * with the exception of the '\r' character, which is stripped for + * historic reasons. Parameter names may not start with a left square + * bracket, an equal sign, a pound sign, or a semicolon, because these + * are used to identify other tokens. + * + * -------------------------------------------------------------------------- ** + */ + +#include "rsync.h" +#include "ifuncs.h" +#include "itypes.h" + +/* -------------------------------------------------------------------------- ** + * Constants... + */ + +#define BUFR_INC 1024 + + +/* -------------------------------------------------------------------------- ** + * Variables... + * + * bufr - pointer to a global buffer. This is probably a kludge, + * but it was the nicest kludge I could think of (for now). + * bSize - The size of the global buffer . + */ + +static char *bufr = NULL; +static int bSize = 0; +static BOOL (*the_sfunc)(char *); +static BOOL (*the_pfunc)(char *, char *); + +/* -------------------------------------------------------------------------- ** + * Functions... + */ + +static int EatWhitespace( FILE *InFile ) + /* ------------------------------------------------------------------------ ** + * Scan past whitespace (see ctype(3C)) and return the first non-whitespace + * character, or newline, or EOF. + * + * Input: InFile - Input source. + * + * Output: The next non-whitespace character in the input stream. + * + * Notes: Because the config files use a line-oriented grammar, we + * explicitly exclude the newline character from the list of + * whitespace characters. + * - Note that both EOF (-1) and the nul character ('\0') are + * considered end-of-file markers. + * + * ------------------------------------------------------------------------ ** + */ + { + int c; + + for( c = getc( InFile ); isspace( c ) && ('\n' != c); c = getc( InFile ) ) + ; + return( c ); + } /* EatWhitespace */ + +static int EatComment( FILE *InFile ) + /* ------------------------------------------------------------------------ ** + * Scan to the end of a comment. + * + * Input: InFile - Input source. + * + * Output: The character that marks the end of the comment. Normally, + * this will be a newline, but it *might* be an EOF. + * + * Notes: Because the config files use a line-oriented grammar, we + * explicitly exclude the newline character from the list of + * whitespace characters. + * - Note that both EOF (-1) and the nul character ('\0') are + * considered end-of-file markers. + * + * ------------------------------------------------------------------------ ** + */ + { + int c; + + for( c = getc( InFile ); ('\n'!=c) && (EOF!=c) && (c>0); c = getc( InFile ) ) + ; + return( c ); + } /* EatComment */ + +static int Continuation( char *line, int pos ) + /* ------------------------------------------------------------------------ ** + * Scan backards within a string to discover if the last non-whitespace + * character is a line-continuation character ('\\'). + * + * Input: line - A pointer to a buffer containing the string to be + * scanned. + * pos - This is taken to be the offset of the end of the + * string. This position is *not* scanned. + * + * Output: The offset of the '\\' character if it was found, or -1 to + * indicate that it was not. + * + * ------------------------------------------------------------------------ ** + */ + { + pos--; + while( pos >= 0 && isSpace(line + pos) ) + pos--; + + return( ((pos >= 0) && ('\\' == line[pos])) ? pos : -1 ); + } /* Continuation */ + + +static BOOL Section( FILE *InFile, BOOL (*sfunc)(char *) ) + /* ------------------------------------------------------------------------ ** + * Scan a section name, and pass the name to function sfunc(). + * + * Input: InFile - Input source. + * sfunc - Pointer to the function to be called if the section + * name is successfully read. + * + * Output: True if the section name was read and True was returned from + * . False if failed or if a lexical error was + * encountered. + * + * ------------------------------------------------------------------------ ** + */ + { + int c; + int i; + int end; + char *func = "params.c:Section() -"; + + i = 0; /* is the offset of the next free byte in bufr[] and */ + end = 0; /* is the current "end of string" offset. In most */ + /* cases these will be the same, but if the last */ + /* character written to bufr[] is a space, then */ + /* will be one less than . */ + + c = EatWhitespace( InFile ); /* We've already got the '['. Scan */ + /* past initial white space. */ + + while( (EOF != c) && (c > 0) ) + { + + /* Check that the buffer is big enough for the next character. */ + if( i > (bSize - 2) ) + { + bSize += BUFR_INC; + bufr = realloc_array( bufr, char, bSize ); + if( NULL == bufr ) + { + rprintf(FLOG, "%s Memory re-allocation failure.", func); + return( False ); + } + } + + /* Handle a single character. */ + switch( c ) + { + case ']': /* Found the closing bracket. */ + bufr[end] = '\0'; + if( 0 == end ) /* Don't allow an empty name. */ + { + rprintf(FLOG, "%s Empty section name in config file.\n", func ); + return( False ); + } + if( !sfunc( bufr ) ) /* Got a valid name. Deal with it. */ + return( False ); + (void)EatComment( InFile ); /* Finish off the line. */ + return( True ); + + case '\n': /* Got newline before closing ']'. */ + i = Continuation( bufr, i ); /* Check for line continuation. */ + if( i < 0 ) + { + bufr[end] = '\0'; + rprintf(FLOG, "%s Badly formed line in config file: %s\n", + func, bufr ); + return( False ); + } + end = ( (i > 0) && (' ' == bufr[i - 1]) ) ? (i - 1) : (i); + c = getc( InFile ); /* Continue with next line. */ + break; + + default: /* All else are a valid name chars. */ + if( isspace( c ) ) /* One space per whitespace region. */ + { + bufr[end] = ' '; + i = end + 1; + c = EatWhitespace( InFile ); + } + else /* All others copy verbatim. */ + { + bufr[i++] = c; + end = i; + c = getc( InFile ); + } + } + } + + /* We arrive here if we've met the EOF before the closing bracket. */ + rprintf(FLOG, "%s Unexpected EOF in the config file: %s\n", func, bufr ); + return( False ); + } /* Section */ + +static BOOL Parameter( FILE *InFile, BOOL (*pfunc)(char *, char *), int c ) + /* ------------------------------------------------------------------------ ** + * Scan a parameter name and value, and pass these two fields to pfunc(). + * + * Input: InFile - The input source. + * pfunc - A pointer to the function that will be called to + * process the parameter, once it has been scanned. + * c - The first character of the parameter name, which + * would have been read by Parse(). Unlike a comment + * line or a section header, there is no lead-in + * character that can be discarded. + * + * Output: True if the parameter name and value were scanned and processed + * successfully, else False. + * + * Notes: This function is in two parts. The first loop scans the + * parameter name. Internal whitespace is compressed, and an + * equal sign (=) terminates the token. Leading and trailing + * whitespace is discarded. The second loop scans the parameter + * value. When both have been successfully identified, they are + * passed to pfunc() for processing. + * + * ------------------------------------------------------------------------ ** + */ + { + int i = 0; /* Position within bufr. */ + int end = 0; /* bufr[end] is current end-of-string. */ + int vstart = 0; /* Starting position of the parameter value. */ + char *func = "params.c:Parameter() -"; + + /* Read the parameter name. */ + while( 0 == vstart ) /* Loop until we've found the start of the value. */ + { + + if( i > (bSize - 2) ) /* Ensure there's space for next char. */ + { + bSize += BUFR_INC; + bufr = realloc_array( bufr, char, bSize ); + if( NULL == bufr ) + { + rprintf(FLOG, "%s Memory re-allocation failure.", func) ; + return( False ); + } + } + + switch( c ) + { + case '=': /* Equal sign marks end of param name. */ + if( 0 == end ) /* Don't allow an empty name. */ + { + rprintf(FLOG, "%s Invalid parameter name in config file.\n", func ); + return( False ); + } + bufr[end++] = '\0'; /* Mark end of string & advance. */ + i = vstart = end; /* New string starts here. */ + c = EatWhitespace(InFile); + break; + + case '\n': /* Find continuation char, else error. */ + i = Continuation( bufr, i ); + if( i < 0 ) + { + bufr[end] = '\0'; + rprintf(FLOG, "%s Ignoring badly formed line in config file: %s\n", + func, bufr ); + return( True ); + } + end = ( (i > 0) && (' ' == bufr[i - 1]) ) ? (i - 1) : (i); + c = getc( InFile ); /* Read past eoln. */ + break; + + case '\0': /* Shouldn't have EOF within param name. */ + case EOF: + bufr[i] = '\0'; + rprintf(FLOG, "%s Unexpected end-of-file at: %s\n", func, bufr ); + return( True ); + + case ' ': + case '\t': + /* A directive divides at the first space or tab. */ + if (*bufr == '&') { + bufr[end++] = '\0'; + i = vstart = end; + c = EatWhitespace(InFile); + if (c == '=') + c = EatWhitespace(InFile); + break; + } + /* FALL THROUGH */ + + default: + if( isspace( c ) ) /* One ' ' per whitespace region. */ + { + bufr[end] = ' '; + i = end + 1; + c = EatWhitespace( InFile ); + } + else /* All others verbatim. */ + { + bufr[i++] = c; + end = i; + c = getc( InFile ); + } + } + } + + /* Now parse the value. */ + while( (EOF !=c) && (c > 0) ) + { + + if( i > (bSize - 2) ) /* Make sure there's enough room. */ + { + bSize += BUFR_INC; + bufr = realloc_array( bufr, char, bSize ); + if( NULL == bufr ) + { + rprintf(FLOG, "%s Memory re-allocation failure.", func) ; + return( False ); + } + } + + switch( c ) + { + case '\r': /* Explicitly remove '\r' because the older */ + c = getc( InFile ); /* version called fgets_slash() which also */ + break; /* removes them. */ + + case '\n': /* Marks end of value unless there's a '\'. */ + i = Continuation( bufr, i ); + if( i < 0 ) + c = 0; + else + { + for( end = i; end >= 0 && isSpace(bufr + end); end-- ) + ; + c = getc( InFile ); + } + break; + + default: /* All others verbatim. Note that spaces do */ + bufr[i++] = c; /* not advance . This allows trimming */ + if( !isspace( c ) ) /* of whitespace at the end of the line. */ + end = i; + c = getc( InFile ); + break; + } + } + bufr[end] = '\0'; /* End of value. */ + + return( pfunc( bufr, &bufr[vstart] ) ); /* Pass name & value to pfunc(). */ + } /* Parameter */ + +static int name_cmp(const void *n1, const void *n2) +{ + return strcmp(*(char * const *)n1, *(char * const *)n2); +} + +static int include_config(char *include, int manage_globals) +{ + STRUCT_STAT sb; + char *match = manage_globals ? "*.conf" : "*.inc"; + int ret; + + if (do_stat(include, &sb) < 0) { + rsyserr(FLOG, errno, "unable to stat config file \"%s\"", include); + return 0; + } + + if (S_ISREG(sb.st_mode)) { + if (manage_globals && the_sfunc) + the_sfunc("]push"); + ret = pm_process(include, the_sfunc, the_pfunc); + if (manage_globals && the_sfunc) + the_sfunc("]pop"); + } else if (S_ISDIR(sb.st_mode)) { + char buf[MAXPATHLEN], **bpp; + item_list conf_list; + struct dirent *di; + size_t j; + DIR *d; + + if (!(d = opendir(include))) { + rsyserr(FLOG, errno, "unable to open config dir \"%s\"", include); + return 0; + } + + memset(&conf_list, 0, sizeof conf_list); + + while ((di = readdir(d)) != NULL) { + char *dname = d_name(di); + if (!wildmatch(match, dname)) + continue; + bpp = EXPAND_ITEM_LIST(&conf_list, char *, 32); + pathjoin(buf, sizeof buf, include, dname); + *bpp = strdup(buf); + } + closedir(d); + + if (!(bpp = conf_list.items)) + return 1; + + if (conf_list.count > 1) + qsort(bpp, conf_list.count, sizeof (char *), name_cmp); + + for (j = 0, ret = 1; j < conf_list.count; j++) { + if (manage_globals && the_sfunc) + the_sfunc(j == 0 ? "]push" : "]reset"); + if ((ret = pm_process(bpp[j], the_sfunc, the_pfunc)) != 1) + break; + } + + if (manage_globals && the_sfunc) + the_sfunc("]pop"); + + for (j = 0; j < conf_list.count; j++) + free(bpp[j]); + free(bpp); + } else + ret = 0; + + return ret; +} + +static int parse_directives(char *name, char *val) +{ + if (strcasecmp(name, "&include") == 0) + return include_config(val, 1); + if (strcasecmp(name, "&merge") == 0) + return include_config(val, 0); + rprintf(FLOG, "Unknown directive: %s.\n", name); + return 0; +} + +static int Parse( FILE *InFile, + BOOL (*sfunc)(char *), + BOOL (*pfunc)(char *, char *) ) + /* ------------------------------------------------------------------------ ** + * Scan & parse the input. + * + * Input: InFile - Input source. + * sfunc - Function to be called when a section name is scanned. + * See Section(). + * pfunc - Function to be called when a parameter is scanned. + * See Parameter(). + * + * Output: 1 if the file was successfully scanned, 2 if the file was + * scanned until a section header with no section function, else 0. + * + * Notes: The input can be viewed in terms of 'lines'. There are four + * types of lines: + * Blank - May contain whitespace, otherwise empty. + * Comment - First non-whitespace character is a ';' or '#'. + * The remainder of the line is ignored. + * Section - First non-whitespace character is a '['. + * Parameter - The default case. + * + * ------------------------------------------------------------------------ ** + */ + { + int c; + + c = EatWhitespace( InFile ); + while( (EOF != c) && (c > 0) ) + { + switch( c ) + { + case '\n': /* Blank line. */ + c = EatWhitespace( InFile ); + break; + + case ';': /* Comment line. */ + case '#': + c = EatComment( InFile ); + break; + + case '[': /* Section Header. */ + if (!sfunc) + return 2; + if( !Section( InFile, sfunc ) ) + return 0; + c = EatWhitespace( InFile ); + break; + + case '\\': /* Bogus backslash. */ + c = EatWhitespace( InFile ); + break; + + case '&': /* Handle directives */ + the_sfunc = sfunc; + the_pfunc = pfunc; + c = Parameter( InFile, parse_directives, c ); + if (c != 1) + return c; + c = EatWhitespace( InFile ); + break; + + default: /* Parameter line. */ + if( !Parameter( InFile, pfunc, c ) ) + return 0; + c = EatWhitespace( InFile ); + break; + } + } + return 1; + } /* Parse */ + +static FILE *OpenConfFile( char *FileName ) + /* ------------------------------------------------------------------------ ** + * Open a config file. + * + * Input: FileName - The pathname of the config file to be opened. + * + * Output: A pointer of type (FILE *) to the opened file, or NULL if the + * file could not be opened. + * + * ------------------------------------------------------------------------ ** + */ + { + FILE *OpenedFile; + char *func = "params.c:OpenConfFile() -"; + + if( NULL == FileName || 0 == *FileName ) + { + rprintf(FLOG, "%s No config filename specified.\n", func); + return( NULL ); + } + + OpenedFile = fopen( FileName, "r" ); + if( NULL == OpenedFile ) + { + rsyserr(FLOG, errno, "unable to open config file \"%s\"", + FileName); + } + + return( OpenedFile ); + } /* OpenConfFile */ + +int pm_process( char *FileName, + BOOL (*sfunc)(char *), + BOOL (*pfunc)(char *, char *) ) + /* ------------------------------------------------------------------------ ** + * Process the named parameter file. + * + * Input: FileName - The pathname of the parameter file to be opened. + * sfunc - A pointer to a function that will be called when + * a section name is discovered. + * pfunc - A pointer to a function that will be called when + * a parameter name and value are discovered. + * + * Output: 1 if the file was successfully parsed, 2 if parsing ended at a + * section header w/o a section function, else 0. + * + * ------------------------------------------------------------------------ ** + */ + { + int result; + FILE *InFile; + char *func = "params.c:pm_process() -"; + + InFile = OpenConfFile( FileName ); /* Open the config file. */ + if( NULL == InFile ) + return( False ); + + if( NULL != bufr ) /* If we already have a buffer */ + result = Parse( InFile, sfunc, pfunc ); /* (recursive call), then just */ + /* use it. */ + + else /* If we don't have a buffer */ + { /* allocate one, then parse, */ + bSize = BUFR_INC; /* then free. */ + bufr = new_array( char, bSize ); + if( NULL == bufr ) + { + rprintf(FLOG, "%s memory allocation failure.\n", func); + fclose(InFile); + return( False ); + } + result = Parse( InFile, sfunc, pfunc ); + free( bufr ); + bufr = NULL; + bSize = 0; + } + + fclose(InFile); + + if( !result ) /* Generic failure. */ + { + rprintf(FLOG, "%s Failed. Error returned from params.c:parse().\n", func); + return 0; + } + + return result; + } /* pm_process */ + +/* -------------------------------------------------------------------------- */ + diff --git a/rsync/pipe.c b/rsync/pipe.c new file mode 100644 index 0000000..4ac316a --- /dev/null +++ b/rsync/pipe.c @@ -0,0 +1,180 @@ +/* + * Routines used to setup various kinds of inter-process pipes. + * + * Copyright (C) 1996-2000 Andrew Tridgell + * Copyright (C) 1996 Paul Mackerras + * Copyright (C) 2001, 2002 Martin Pool + * Copyright (C) 2004-2014 Wayne Davison + * + * 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, visit the http://fsf.org website. + */ + +#include "rsync.h" + +extern int am_sender; +extern int am_server; +extern int blocking_io; +extern int filesfrom_fd; +extern int munge_symlinks; +extern char *logfile_name; +extern int remote_option_cnt; +extern const char **remote_options; +extern struct chmod_mode_struct *chmod_modes; + +/** + * Create a child connected to us via its stdin/stdout. + * + * This is derived from CVS code + * + * Note that in the child STDIN is set to blocking and STDOUT + * is set to non-blocking. This is necessary as rsh relies on stdin being blocking + * and ssh relies on stdout being non-blocking + * + * If blocking_io is set then use blocking io on both fds. That can be + * used to cope with badly broken rsh implementations like the one on + * Solaris. + **/ +pid_t piped_child(char **command, int *f_in, int *f_out) +{ + pid_t pid; + int to_child_pipe[2]; + int from_child_pipe[2]; + + if (DEBUG_GTE(CMD, 1)) + print_child_argv("opening connection using:", command); + + if (fd_pair(to_child_pipe) < 0 || fd_pair(from_child_pipe) < 0) { + rsyserr(FERROR, errno, "pipe"); + exit_cleanup(RERR_IPC); + } + + pid = do_fork(); + if (pid == -1) { + rsyserr(FERROR, errno, "fork"); + exit_cleanup(RERR_IPC); + } + + if (pid == 0) { + if (dup2(to_child_pipe[0], STDIN_FILENO) < 0 || + close(to_child_pipe[1]) < 0 || + close(from_child_pipe[0]) < 0 || + dup2(from_child_pipe[1], STDOUT_FILENO) < 0) { + rsyserr(FERROR, errno, "Failed to dup/close"); + exit_cleanup(RERR_IPC); + } + if (to_child_pipe[0] != STDIN_FILENO) + close(to_child_pipe[0]); + if (from_child_pipe[1] != STDOUT_FILENO) + close(from_child_pipe[1]); + set_blocking(STDIN_FILENO); + if (blocking_io > 0) + set_blocking(STDOUT_FILENO); + execvp(command[0], command); + rsyserr(FERROR, errno, "Failed to exec %s", command[0]); + exit_cleanup(RERR_IPC); + } + + if (close(from_child_pipe[1]) < 0 || close(to_child_pipe[0]) < 0) { + rsyserr(FERROR, errno, "Failed to close"); + exit_cleanup(RERR_IPC); + } + + *f_in = from_child_pipe[0]; + *f_out = to_child_pipe[1]; + + return pid; +} + +/* This function forks a child which calls child_main(). First, + * however, it has to establish communication paths to and from the + * newborn child. It creates two socket pairs -- one for writing to + * the child (from the parent) and one for reading from the child + * (writing to the parent). Since that's four socket ends, each + * process has to close the two ends it doesn't need. The remaining + * two socket ends are retained for reading and writing. In the + * child, the STDIN and STDOUT file descriptors refer to these + * sockets. In the parent, the function arguments f_in and f_out are + * set to refer to these sockets. */ +pid_t local_child(int argc, char **argv, int *f_in, int *f_out, + int (*child_main)(int, char*[])) +{ + pid_t pid; + int to_child_pipe[2]; + int from_child_pipe[2]; + + /* The parent process is always the sender for a local rsync. */ + assert(am_sender); + + if (fd_pair(to_child_pipe) < 0 || + fd_pair(from_child_pipe) < 0) { + rsyserr(FERROR, errno, "pipe"); + exit_cleanup(RERR_IPC); + } + + pid = do_fork(); + if (pid == -1) { + rsyserr(FERROR, errno, "fork"); + exit_cleanup(RERR_IPC); + } + + if (pid == 0) { + am_sender = 0; + am_server = 1; + filesfrom_fd = -1; + munge_symlinks = 0; /* Each side needs its own option. */ + chmod_modes = NULL; /* Let the sending side handle this. */ + + /* Let the client side handle this. */ + if (logfile_name) { + logfile_name = NULL; + logfile_close(); + } + + if (remote_option_cnt) { + int rc = remote_option_cnt + 1; + const char **rv = remote_options; + if (!parse_arguments(&rc, &rv)) { + option_error(); + exit_cleanup(RERR_SYNTAX); + } + } + + if (dup2(to_child_pipe[0], STDIN_FILENO) < 0 || + close(to_child_pipe[1]) < 0 || + close(from_child_pipe[0]) < 0 || + dup2(from_child_pipe[1], STDOUT_FILENO) < 0) { + rsyserr(FERROR, errno, "Failed to dup/close"); + exit_cleanup(RERR_IPC); + } + if (to_child_pipe[0] != STDIN_FILENO) + close(to_child_pipe[0]); + if (from_child_pipe[1] != STDOUT_FILENO) + close(from_child_pipe[1]); +#ifdef ICONV_CONST + setup_iconv(); +#endif + child_main(argc, argv); + } + + if (close(from_child_pipe[1]) < 0 || + close(to_child_pipe[0]) < 0) { + rsyserr(FERROR, errno, "Failed to close"); + exit_cleanup(RERR_IPC); + } + + *f_in = from_child_pipe[0]; + *f_out = to_child_pipe[1]; + + return pid; +} diff --git a/rsync/popt/CHANGES b/rsync/popt/CHANGES new file mode 100644 index 0000000..db16a5f --- /dev/null +++ b/rsync/popt/CHANGES @@ -0,0 +1,46 @@ +1.5 -> 1.6 + - add ability to perform callbacks for every, not just first, match. + +1.3 -> 1.5 + - heavy dose of const's + - poptParseArgvString() now NULL terminates the list + +1.2.3 -> 1.3 + - added support for single - + - misc bug fixes + - portability improvements + +1.2.2 -> 1.2.3 + - fixed memset() in help message generation (Dale Hawkins) + - added extern "C" stuff to popt.h for C++ compilers (Dale Hawkins) + - const'ified poptParseArgvString (Jeff Garzik) + +1.2.1 -> 1.2.2 + - fixed bug in chaind alias happens which seems to have only + affected --triggers in rpm + - added POPT_ARG_VAL + - popt.3 installed by default + +1.2 -> 1.2.1 + - added POPT_ARG_INTL_DOMAIN (Elliot Lee) + - updated Makefile's to be more GNUish (Elliot Lee) + +1.1 -> 1.2 + - added popt.3 man page (Robert Lynch) + - don't use mmap anymore (its lack of portability isn't worth the + trouble) + - added test script + - added support for exec + - removed support for *_POPT_ALIASES env variable -- it was a bad + idea + - reorganized into multiple source files + - added automatic help generation, POPT_AUTOHELP + - added table callbacks + - added table inclusion + - updated man page for new features + - added test scripts + +1.0 -> 1.1 + - moved to autoconf (Fred Fish) + - added STRERROR replacement (Norbert Warmuth) + - added const keywords (Bruce Perens) diff --git a/rsync/popt/COPYING b/rsync/popt/COPYING new file mode 100644 index 0000000..b4c7ca8 --- /dev/null +++ b/rsync/popt/COPYING @@ -0,0 +1,22 @@ +Copyright (c) 1998 Red Hat Software + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of the X Consortium shall not be +used in advertising or otherwise to promote the sale, use or other dealings +in this Software without prior written authorization from the X Consortium. diff --git a/rsync/popt/README b/rsync/popt/README new file mode 100644 index 0000000..0b5205b --- /dev/null +++ b/rsync/popt/README @@ -0,0 +1,18 @@ +This is the popt command line option parsing library. While it is similiar +to getopt(3), it contains a number of enhancements, including: + + 1) popt is fully reentrant + 2) popt can parse arbitrary argv[] style arrays while + getopt(2) makes this quite difficult + 3) popt allows users to alias command line arguments + 4) popt provides convience functions for parsing strings + into argv[] style arrays + +popt is used by rpm, the Red Hat install program, and many other Red Hat +utilities, all of which provide excellent examples of how to use popt. +Complete documentation on popt is available in popt.ps (included in this +tarball), which is excerpted with permission from the book "Linux +Application Development" by Michael K. Johnson and Erik Troan (availble +from Addison Wesley in May, 1998). + +Comments on popt should be addressed to ewt@redhat.com. diff --git a/rsync/popt/README.rsync b/rsync/popt/README.rsync new file mode 100644 index 0000000..1cf54d5 --- /dev/null +++ b/rsync/popt/README.rsync @@ -0,0 +1,4 @@ +This is a perfectly ordinary copy of libpopt. It is only used on platforms +that do not have a sufficiently up-to-date copy of their own. If you build +rsync on a platform which has popt, this directory should not be used. (You +can control that using the --with-included-popt configure flag.) diff --git a/rsync/popt/dummy.in b/rsync/popt/dummy.in new file mode 100644 index 0000000..e69de29 diff --git a/rsync/popt/findme.c b/rsync/popt/findme.c new file mode 100644 index 0000000..ac4cbae --- /dev/null +++ b/rsync/popt/findme.c @@ -0,0 +1,55 @@ +/** \ingroup popt + * \file popt/findme.c + */ + +/* (C) 1998-2002 Red Hat, Inc. -- Licensing details are in the COPYING + file accompanying popt source distributions, available from + ftp://ftp.rpm.org/pub/rpm/dist. */ + +#include "system.h" +#include "findme.h" + +const char * findProgramPath(const char * argv0) +{ + char * path = getenv("PATH"); + char * pathbuf; + char * start, * chptr; + char * buf; + size_t bufsize; + + if (argv0 == NULL) return NULL; /* XXX can't happen */ + /* If there is a / in the argv[0], it has to be an absolute path */ + if (strchr(argv0, '/')) + return xstrdup(argv0); + + if (path == NULL) return NULL; + + bufsize = strlen(path) + 1; + start = pathbuf = alloca(bufsize); + if (pathbuf == NULL) return NULL; /* XXX can't happen */ + strlcpy(pathbuf, path, bufsize); + bufsize += sizeof "/" - 1 + strlen(argv0); + buf = malloc(bufsize); + if (buf == NULL) return NULL; /* XXX can't happen */ + + chptr = NULL; + /*@-branchstate@*/ + do { + if ((chptr = strchr(start, ':'))) + *chptr = '\0'; + snprintf(buf, bufsize, "%s/%s", start, argv0); + + if (!access(buf, X_OK)) + return buf; + + if (chptr) + start = chptr + 1; + else + start = NULL; + } while (start && *start); + /*@=branchstate@*/ + + free(buf); + + return NULL; +} diff --git a/rsync/popt/findme.h b/rsync/popt/findme.h new file mode 100644 index 0000000..a016b86 --- /dev/null +++ b/rsync/popt/findme.h @@ -0,0 +1,20 @@ +/** \ingroup popt + * \file popt/findme.h + */ + +/* (C) 1998-2000 Red Hat, Inc. -- Licensing details are in the COPYING + file accompanying popt source distributions, available from + ftp://ftp.rpm.org/pub/rpm/dist. */ + +#ifndef H_FINDME +#define H_FINDME + +/** + * Return absolute path to executable by searching PATH. + * @param argv0 name of executable + * @return (malloc'd) absolute path to executable (or NULL) + */ +/*@null@*/ const char * findProgramPath(/*@null@*/ const char * argv0) + /*@*/; + +#endif diff --git a/rsync/popt/popt.c b/rsync/popt/popt.c new file mode 100644 index 0000000..ec6f3bd --- /dev/null +++ b/rsync/popt/popt.c @@ -0,0 +1,1283 @@ +/** \ingroup popt + * \file popt/popt.c + */ + +/* (C) 1998-2002 Red Hat, Inc. -- Licensing details are in the COPYING + file accompanying popt source distributions, available from + ftp://ftp.rpm.org/pub/rpm/dist */ + +#undef MYDEBUG + +#include "system.h" + +#if HAVE_FLOAT_H +#include +#endif +#include + +#include "findme.h" +#include "poptint.h" + +#ifndef DBL_EPSILON +#define DBL_EPSILON 2.2204460492503131e-16 +#endif + +#ifdef MYDEBUG +/*@unchecked@*/ +int _popt_debug = 0; +#endif + +#if !defined(HAVE_STRERROR) && !defined(__LCLINT__) +static char * strerror(int errno) +{ + extern int sys_nerr; + extern char * sys_errlist[]; + + if ((0 <= errno) && (errno < sys_nerr)) + return sys_errlist[errno]; + else + return POPT_("unknown errno"); +} +#endif + +#ifdef MYDEBUG +/*@unused@*/ +static void prtcon(const char *msg, poptContext con) +{ + if (msg) fprintf(stderr, "%s", msg); + fprintf(stderr, "\tcon %p os %p nextCharArg \"%s\" nextArg \"%s\" argv[%d] \"%s\"\n", + con, con->os, + (con->os->nextCharArg ? con->os->nextCharArg : ""), + (con->os->nextArg ? con->os->nextArg : ""), + con->os->next, + (con->os->argv && con->os->argv[con->os->next] + ? con->os->argv[con->os->next] : "")); +} +#endif + +void poptSetExecPath(poptContext con, const char * path, int allowAbsolute) +{ + con->execPath = _free(con->execPath); + con->execPath = xstrdup(path); + con->execAbsolute = allowAbsolute; + /*@-nullstate@*/ /* LCL: con->execPath not NULL */ + return; + /*@=nullstate@*/ +} + +static void invokeCallbacksPRE(poptContext con, const struct poptOption * opt) + /*@globals internalState@*/ + /*@modifies internalState@*/ +{ + if (opt != NULL) + for (; opt->longName || opt->shortName || opt->arg; opt++) { + if (opt->arg == NULL) continue; /* XXX program error. */ + if ((opt->argInfo & POPT_ARG_MASK) == POPT_ARG_INCLUDE_TABLE) { + void * arg = opt->arg; +/*@-branchstate@*/ + /* XXX sick hack to preserve pretense of ABI. */ + if (arg == poptHelpOptions) arg = poptHelpOptionsI18N; +/*@=branchstate@*/ + /* Recurse on included sub-tables. */ + invokeCallbacksPRE(con, arg); + } else if ((opt->argInfo & POPT_ARG_MASK) == POPT_ARG_CALLBACK && + (opt->argInfo & POPT_CBFLAG_PRE)) + { /*@-castfcnptr@*/ + poptCallbackType cb = (poptCallbackType)opt->arg; + /*@=castfcnptr@*/ + /* Perform callback. */ + /*@-noeffectuncon @*/ + cb(con, POPT_CALLBACK_REASON_PRE, NULL, NULL, opt->descrip); + /*@=noeffectuncon @*/ + } + } +} + +static void invokeCallbacksPOST(poptContext con, const struct poptOption * opt) + /*@globals internalState@*/ + /*@modifies internalState@*/ +{ + if (opt != NULL) + for (; opt->longName || opt->shortName || opt->arg; opt++) { + if (opt->arg == NULL) continue; /* XXX program error. */ + if ((opt->argInfo & POPT_ARG_MASK) == POPT_ARG_INCLUDE_TABLE) { + void * arg = opt->arg; +/*@-branchstate@*/ + /* XXX sick hack to preserve pretense of ABI. */ + if (arg == poptHelpOptions) arg = poptHelpOptionsI18N; +/*@=branchstate@*/ + /* Recurse on included sub-tables. */ + invokeCallbacksPOST(con, arg); + } else if ((opt->argInfo & POPT_ARG_MASK) == POPT_ARG_CALLBACK && + (opt->argInfo & POPT_CBFLAG_POST)) + { /*@-castfcnptr@*/ + poptCallbackType cb = (poptCallbackType)opt->arg; + /*@=castfcnptr@*/ + /* Perform callback. */ + /*@-noeffectuncon @*/ + cb(con, POPT_CALLBACK_REASON_POST, NULL, NULL, opt->descrip); + /*@=noeffectuncon @*/ + } + } +} + +static void invokeCallbacksOPTION(poptContext con, + const struct poptOption * opt, + const struct poptOption * myOpt, + /*@null@*/ const void * myData, int shorty) + /*@globals internalState@*/ + /*@modifies internalState@*/ +{ + const struct poptOption * cbopt = NULL; + + if (opt != NULL) + for (; opt->longName || opt->shortName || opt->arg; opt++) { + if ((opt->argInfo & POPT_ARG_MASK) == POPT_ARG_INCLUDE_TABLE) { + void * arg = opt->arg; +/*@-branchstate@*/ + /* XXX sick hack to preserve pretense of ABI. */ + if (arg == poptHelpOptions) arg = poptHelpOptionsI18N; +/*@=branchstate@*/ + /* Recurse on included sub-tables. */ + if (opt->arg != NULL) /* XXX program error */ + invokeCallbacksOPTION(con, opt->arg, myOpt, myData, shorty); + } else if ((opt->argInfo & POPT_ARG_MASK) == POPT_ARG_CALLBACK && + !(opt->argInfo & POPT_CBFLAG_SKIPOPTION)) { + /* Save callback info. */ + cbopt = opt; + } else if (cbopt != NULL && + ((myOpt->shortName && opt->shortName && shorty && + myOpt->shortName == opt->shortName) || + (myOpt->longName && opt->longName && + /*@-nullpass@*/ /* LCL: opt->longName != NULL */ + !strcmp(myOpt->longName, opt->longName))) + /*@=nullpass@*/ + ) + { /*@-castfcnptr@*/ + poptCallbackType cb = (poptCallbackType)cbopt->arg; + /*@=castfcnptr@*/ + const void * cbData = (cbopt->descrip ? cbopt->descrip : myData); + /* Perform callback. */ + if (cb != NULL) { /* XXX program error */ + /*@-noeffectuncon @*/ + cb(con, POPT_CALLBACK_REASON_OPTION, myOpt, + con->os->nextArg, cbData); + /*@=noeffectuncon @*/ + } + /* Terminate (unless explcitly continuing). */ + if (!(cbopt->argInfo & POPT_CBFLAG_CONTINUE)) + return; + } + } +} + +poptContext poptGetContext(const char * name, int argc, const char ** argv, + const struct poptOption * options, int flags) +{ + poptContext con = malloc(sizeof(*con)); + + if (con == NULL) return NULL; /* XXX can't happen */ + memset(con, 0, sizeof(*con)); + + con->os = con->optionStack; + con->os->argc = argc; + /*@-dependenttrans -assignexpose@*/ /* FIX: W2DO? */ + con->os->argv = argv; + /*@=dependenttrans =assignexpose@*/ + con->os->argb = NULL; + + if (!(flags & POPT_CONTEXT_KEEP_FIRST)) + con->os->next = 1; /* skip argv[0] */ + + con->leftovers = calloc( (argc + 1), sizeof(*con->leftovers) ); + /*@-dependenttrans -assignexpose@*/ /* FIX: W2DO? */ + con->options = options; + /*@=dependenttrans =assignexpose@*/ + con->aliases = NULL; + con->numAliases = 0; + con->flags = flags; + con->execs = NULL; + con->numExecs = 0; + con->finalArgvAlloced = argc * 2; + con->finalArgv = calloc( con->finalArgvAlloced, sizeof(*con->finalArgv) ); + con->execAbsolute = 1; + con->arg_strip = NULL; + + if (getenv("POSIXLY_CORRECT") || getenv("POSIX_ME_HARDER")) + con->flags |= POPT_CONTEXT_POSIXMEHARDER; + + if (name) { + size_t bufsize = strlen(name) + 1; + char * t = malloc(bufsize); + if (t) { + strlcpy(t, name, bufsize); + con->appName = t; + } + } + + /*@-internalglobs@*/ + invokeCallbacksPRE(con, con->options); + /*@=internalglobs@*/ + + return con; +} + +static void cleanOSE(/*@special@*/ struct optionStackEntry *os) + /*@uses os @*/ + /*@releases os->nextArg, os->argv, os->argb @*/ + /*@modifies os @*/ +{ + os->nextArg = _free(os->nextArg); + os->argv = _free(os->argv); + os->argb = PBM_FREE(os->argb); +} + +/*@-boundswrite@*/ +void poptResetContext(poptContext con) +{ + int i; + + if (con == NULL) return; + while (con->os > con->optionStack) { + cleanOSE(con->os--); + } + con->os->argb = PBM_FREE(con->os->argb); + con->os->currAlias = NULL; + con->os->nextCharArg = NULL; + con->os->nextArg = NULL; + con->os->next = 1; /* skip argv[0] */ + + con->numLeftovers = 0; + con->nextLeftover = 0; + con->restLeftover = 0; + con->doExec = NULL; + + if (con->finalArgv != NULL) + for (i = 0; i < con->finalArgvCount; i++) { + /*@-unqualifiedtrans@*/ /* FIX: typedef double indirection. */ + con->finalArgv[i] = _free(con->finalArgv[i]); + /*@=unqualifiedtrans@*/ + } + + con->finalArgvCount = 0; + con->arg_strip = PBM_FREE(con->arg_strip); + /*@-nullstate@*/ /* FIX: con->finalArgv != NULL */ + return; + /*@=nullstate@*/ +} +/*@=boundswrite@*/ + +/* Only one of longName, shortName should be set, not both. */ +/*@-boundswrite@*/ +static int handleExec(/*@special@*/ poptContext con, + /*@null@*/ const char * longName, char shortName) + /*@uses con->execs, con->numExecs, con->flags, con->doExec, + con->finalArgv, con->finalArgvAlloced, con->finalArgvCount @*/ + /*@modifies con @*/ +{ + poptItem item; + int i; + + if (con->execs == NULL || con->numExecs <= 0) /* XXX can't happen */ + return 0; + + for (i = con->numExecs - 1; i >= 0; i--) { + item = con->execs + i; + if (longName && !(item->option.longName && + !strcmp(longName, item->option.longName))) + continue; + else if (shortName != item->option.shortName) + continue; + break; + } + if (i < 0) return 0; + + + if (con->flags & POPT_CONTEXT_NO_EXEC) + return 1; + + if (con->doExec == NULL) { + con->doExec = con->execs + i; + return 1; + } + + /* We already have an exec to do; remember this option for next + time 'round */ + if ((con->finalArgvCount + 1) >= (con->finalArgvAlloced)) { + con->finalArgvAlloced += 10; + con->finalArgv = realloc(con->finalArgv, + sizeof(*con->finalArgv) * con->finalArgvAlloced); + } + + i = con->finalArgvCount++; + if (con->finalArgv != NULL) /* XXX can't happen */ + { size_t bufsize = (longName ? strlen(longName) : 0) + 3; + char *s = malloc(bufsize); + if (s != NULL) { /* XXX can't happen */ + if (longName) + snprintf(s, bufsize, "--%s", longName); + else + snprintf(s, bufsize, "-%c", shortName); + con->finalArgv[i] = s; + } else + con->finalArgv[i] = NULL; + } + + /*@-nullstate@*/ /* FIX: con->finalArgv[] == NULL */ + return 1; + /*@=nullstate@*/ +} +/*@=boundswrite@*/ + +/* Only one of longName, shortName may be set at a time */ +static int handleAlias(/*@special@*/ poptContext con, + /*@null@*/ const char * longName, char shortName, + /*@exposed@*/ /*@null@*/ const char * nextCharArg) + /*@uses con->aliases, con->numAliases, con->optionStack, con->os, + con->os->currAlias, con->os->currAlias->option.longName @*/ + /*@modifies con @*/ +{ + poptItem item = con->os->currAlias; + int rc; + int i; + + if (item) { + if (longName && (item->option.longName && + !strcmp(longName, item->option.longName))) + return 0; + if (shortName && shortName == item->option.shortName) + return 0; + } + + if (con->aliases == NULL || con->numAliases <= 0) /* XXX can't happen */ + return 0; + + for (i = con->numAliases - 1; i >= 0; i--) { + item = con->aliases + i; + if (longName && !(item->option.longName && + !strcmp(longName, item->option.longName))) + continue; + else if (shortName != item->option.shortName) + continue; + break; + } + if (i < 0) return 0; + + if ((con->os - con->optionStack + 1) == POPT_OPTION_DEPTH) + return POPT_ERROR_OPTSTOODEEP; + +/*@-boundsread@*/ + if (nextCharArg && *nextCharArg) + con->os->nextCharArg = nextCharArg; +/*@=boundsread@*/ + + con->os++; + con->os->next = 0; + con->os->stuffed = 0; + con->os->nextArg = NULL; + con->os->nextCharArg = NULL; + con->os->currAlias = con->aliases + i; + rc = poptDupArgv(con->os->currAlias->argc, con->os->currAlias->argv, + &con->os->argc, &con->os->argv); + con->os->argb = NULL; + + return (rc ? rc : 1); +} + +/*@-bounds -boundswrite @*/ +static int execCommand(poptContext con) + /*@globals internalState @*/ + /*@modifies internalState @*/ +{ + poptItem item = con->doExec; + const char ** argv; + int argc = 0; + + if (item == NULL) /*XXX can't happen*/ + return POPT_ERROR_NOARG; + + if (item->argv == NULL || item->argc < 1 || + (!con->execAbsolute && strchr(item->argv[0], '/'))) + return POPT_ERROR_NOARG; + + argv = malloc(sizeof(*argv) * + (6 + item->argc + con->numLeftovers + con->finalArgvCount)); + if (argv == NULL) return POPT_ERROR_MALLOC; + + if (!strchr(item->argv[0], '/') && con->execPath != NULL) { + size_t bufsize = strlen(con->execPath) + strlen(item->argv[0]) + sizeof "/"; + char *s = alloca(bufsize); + snprintf(s, bufsize, "%s/%s", con->execPath, item->argv[0]); + argv[argc] = s; + } else + argv[argc] = findProgramPath(item->argv[0]); + if (argv[argc++] == NULL) return POPT_ERROR_NOARG; + + if (item->argc > 1) { + memcpy(argv + argc, item->argv + 1, sizeof(*argv) * (item->argc - 1)); + argc += (item->argc - 1); + } + + if (con->finalArgv != NULL && con->finalArgvCount > 0) { + memcpy(argv + argc, con->finalArgv, + sizeof(*argv) * con->finalArgvCount); + argc += con->finalArgvCount; + } + + if (con->leftovers != NULL && con->numLeftovers > 0) { + memcpy(argv + argc, con->leftovers, sizeof(*argv) * con->numLeftovers); + argc += con->numLeftovers; + } + + argv[argc] = NULL; + + { +#ifdef __hpux + int rc = setresgid(getgid(), getgid(),-1); + if (rc) return POPT_ERROR_ERRNO; + rc = setresuid(getuid(), getuid(),-1); + if (rc) return POPT_ERROR_ERRNO; +#else +/* + * XXX " ... on BSD systems setuid() should be preferred over setreuid()" + * XXX sez' Timur Bakeyev + * XXX from Norbert Warmuth + */ +#if defined(HAVE_SETUID) + int rc = setgid(getgid()); + if (rc) return POPT_ERROR_ERRNO; + rc = setuid(getuid()); + if (rc) return POPT_ERROR_ERRNO; +#elif defined (HAVE_SETREUID) + int rc = setregid(getgid(), getgid()); + if (rc) return POPT_ERROR_ERRNO; + rc = setreuid(getuid(), getuid()); + if (rc) return POPT_ERROR_ERRNO; +#else + ; /* Can't drop privileges */ +#endif +#endif + } + + if (argv[0] == NULL) + return POPT_ERROR_NOARG; + +#ifdef MYDEBUG +if (_popt_debug) + { const char ** avp; + fprintf(stderr, "==> execvp(%s) argv[%d]:", argv[0], argc); + for (avp = argv; *avp; avp++) + fprintf(stderr, " '%s'", *avp); + fprintf(stderr, "\n"); + } +#endif + + execvp(argv[0], (char *const *)argv); + + return POPT_ERROR_ERRNO; +} +/*@=bounds =boundswrite @*/ + +/*@-boundswrite@*/ +/*@observer@*/ /*@null@*/ static const struct poptOption * +findOption(const struct poptOption * opt, /*@null@*/ const char * longName, + char shortName, + /*@null@*/ /*@out@*/ poptCallbackType * callback, + /*@null@*/ /*@out@*/ const void ** callbackData, + int singleDash) + /*@modifies *callback, *callbackData */ +{ + const struct poptOption * cb = NULL; + + /* This happens when a single - is given */ + if (singleDash && !shortName && (longName && *longName == '\0')) + shortName = '-'; + + for (; opt->longName || opt->shortName || opt->arg; opt++) { + + if ((opt->argInfo & POPT_ARG_MASK) == POPT_ARG_INCLUDE_TABLE) { + const struct poptOption * opt2; + void * arg = opt->arg; + +/*@-branchstate@*/ + /* XXX sick hack to preserve pretense of ABI. */ + if (arg == poptHelpOptions) arg = poptHelpOptionsI18N; +/*@=branchstate@*/ + /* Recurse on included sub-tables. */ + if (arg == NULL) continue; /* XXX program error */ + opt2 = findOption(arg, longName, shortName, callback, + callbackData, singleDash); + if (opt2 == NULL) continue; + /* Sub-table data will be inheirited if no data yet. */ + if (!(callback && *callback)) return opt2; + if (!(callbackData && *callbackData == NULL)) return opt2; + /*@-observertrans -dependenttrans @*/ + *callbackData = opt->descrip; + /*@=observertrans =dependenttrans @*/ + return opt2; + } else if ((opt->argInfo & POPT_ARG_MASK) == POPT_ARG_CALLBACK) { + cb = opt; + } else if (longName && opt->longName && + (!singleDash || (opt->argInfo & POPT_ARGFLAG_ONEDASH)) && + /*@-nullpass@*/ /* LCL: opt->longName != NULL */ + !strcmp(longName, opt->longName)) + /*@=nullpass@*/ + { + break; + } else if (shortName && shortName == opt->shortName) { + break; + } + } + + if (!opt->longName && !opt->shortName) + return NULL; + /*@-modobserver -mods @*/ + if (callback) *callback = NULL; + if (callbackData) *callbackData = NULL; + if (cb) { + if (callback) + /*@-castfcnptr@*/ + *callback = (poptCallbackType)cb->arg; + /*@=castfcnptr@*/ + if (!(cb->argInfo & POPT_CBFLAG_INC_DATA)) { + if (callbackData) + /*@-observertrans@*/ /* FIX: typedef double indirection. */ + *callbackData = cb->descrip; + /*@=observertrans@*/ + } + } + /*@=modobserver =mods @*/ + + return opt; +} +/*@=boundswrite@*/ + +static const char * findNextArg(/*@special@*/ poptContext con, + unsigned argx, int delete_arg) + /*@uses con->optionStack, con->os, + con->os->next, con->os->argb, con->os->argc, con->os->argv @*/ + /*@modifies con @*/ +{ + struct optionStackEntry * os = con->os; + const char * arg; + + do { + int i; + arg = NULL; + while (os->next == os->argc && os > con->optionStack) os--; + if (os->next == os->argc && os == con->optionStack) break; + if (os->argv != NULL) + for (i = os->next; i < os->argc; i++) { + /*@-sizeoftype@*/ + if (os->argb && PBM_ISSET(i, os->argb)) + /*@innercontinue@*/ continue; + if (*os->argv[i] == '-') + /*@innercontinue@*/ continue; + if (--argx > 0) + /*@innercontinue@*/ continue; + arg = os->argv[i]; + if (delete_arg) { + if (os->argb == NULL) os->argb = PBM_ALLOC(os->argc); + if (os->argb != NULL) /* XXX can't happen */ + PBM_SET(i, os->argb); + } + /*@innerbreak@*/ break; + /*@=sizeoftype@*/ + } + if (os > con->optionStack) os--; + } while (arg == NULL); + return arg; +} + +/*@-boundswrite@*/ +static /*@only@*/ /*@null@*/ const char * +expandNextArg(/*@special@*/ poptContext con, const char * s) + /*@uses con->optionStack, con->os, + con->os->next, con->os->argb, con->os->argc, con->os->argv @*/ + /*@modifies con @*/ +{ + const char * a = NULL; + size_t alen, pos; + char *t, *te; + size_t tn = strlen(s) + 1; + char c; + + te = t = malloc(tn);; + if (t == NULL) return NULL; /* XXX can't happen */ + while ((c = *s++) != '\0') { + switch (c) { +#if 0 /* XXX can't do this */ + case '\\': /* escape */ + c = *s++; + /*@switchbreak@*/ break; +#endif + case '!': + if (!(s[0] == '#' && s[1] == ':' && s[2] == '+')) + /*@switchbreak@*/ break; + /* XXX Make sure that findNextArg deletes only next arg. */ + if (a == NULL) { + if ((a = findNextArg(con, 1, 1)) == NULL) + /*@switchbreak@*/ break; + } + s += 3; + + alen = strlen(a); + tn += alen; + pos = te - t; + t = realloc(t, tn); + te = t + pos; + strncpy(te, a, alen); te += alen; + continue; + /*@notreached@*/ /*@switchbreak@*/ break; + default: + /*@switchbreak@*/ break; + } + *te++ = c; + } + *te = '\0'; + t = realloc(t, strlen(t) + 1); /* XXX memory leak, hard to plug */ + return t; +} +/*@=boundswrite@*/ + +static void poptStripArg(/*@special@*/ poptContext con, int which) + /*@uses con->arg_strip, con->optionStack @*/ + /*@defines con->arg_strip @*/ + /*@modifies con @*/ +{ + /*@-sizeoftype@*/ + if (con->arg_strip == NULL) + con->arg_strip = PBM_ALLOC(con->optionStack[0].argc); + if (con->arg_strip != NULL) /* XXX can't happen */ + PBM_SET(which, con->arg_strip); + /*@=sizeoftype@*/ + /*@-compdef@*/ /* LCL: con->arg_strip udefined? */ + return; + /*@=compdef@*/ +} + +int poptSaveLong(long * arg, int argInfo, long aLong) +{ + /* XXX Check alignment, may fail on funky platforms. */ + if (arg == NULL || (((unsigned long)arg) & (sizeof(*arg)-1))) + return POPT_ERROR_NULLARG; + + if (argInfo & POPT_ARGFLAG_NOT) + aLong = ~aLong; + switch (argInfo & POPT_ARGFLAG_LOGICALOPS) { + case 0: + *arg = aLong; + break; + case POPT_ARGFLAG_OR: + *arg |= aLong; + break; + case POPT_ARGFLAG_AND: + *arg &= aLong; + break; + case POPT_ARGFLAG_XOR: + *arg ^= aLong; + break; + default: + return POPT_ERROR_BADOPERATION; + /*@notreached@*/ break; + } + return 0; +} + +int poptSaveInt(/*@null@*/ int * arg, int argInfo, long aLong) +{ + /* XXX Check alignment, may fail on funky platforms. */ + if (arg == NULL || (((unsigned long)arg) & (sizeof(*arg)-1))) + return POPT_ERROR_NULLARG; + + if (argInfo & POPT_ARGFLAG_NOT) + aLong = ~aLong; + switch (argInfo & POPT_ARGFLAG_LOGICALOPS) { + case 0: + *arg = aLong; + break; + case POPT_ARGFLAG_OR: + *arg |= aLong; + break; + case POPT_ARGFLAG_AND: + *arg &= aLong; + break; + case POPT_ARGFLAG_XOR: + *arg ^= aLong; + break; + default: + return POPT_ERROR_BADOPERATION; + /*@notreached@*/ break; + } + return 0; +} + +/*@-boundswrite@*/ +/* returns 'val' element, -1 on last item, POPT_ERROR_* on error */ +int poptGetNextOpt(poptContext con) +{ + const struct poptOption * opt = NULL; + int done = 0; + + if (con == NULL) + return -1; + while (!done) { + const char * origOptString = NULL; + poptCallbackType cb = NULL; + const void * cbData = NULL; + const char * longArg = NULL; + int canstrip = 0; + int shorty = 0; + + while (!con->os->nextCharArg && con->os->next == con->os->argc + && con->os > con->optionStack) { + cleanOSE(con->os--); + } + if (!con->os->nextCharArg && con->os->next == con->os->argc) { + /*@-internalglobs@*/ + invokeCallbacksPOST(con, con->options); + /*@=internalglobs@*/ + if (con->doExec) return execCommand(con); + return -1; + } + + /* Process next long option */ + if (!con->os->nextCharArg) { + char * localOptString, * optString; + int thisopt; + + /*@-sizeoftype@*/ + if (con->os->argb && PBM_ISSET(con->os->next, con->os->argb)) { + con->os->next++; + continue; + } + /*@=sizeoftype@*/ + thisopt = con->os->next; + if (con->os->argv != NULL) /* XXX can't happen */ + origOptString = con->os->argv[con->os->next++]; + + if (origOptString == NULL) /* XXX can't happen */ + return POPT_ERROR_BADOPT; + + if (con->restLeftover || *origOptString != '-') { + if (con->flags & POPT_CONTEXT_POSIXMEHARDER) + con->restLeftover = 1; + if (con->flags & POPT_CONTEXT_ARG_OPTS) { + con->os->nextArg = xstrdup(origOptString); + return 0; + } + if (con->leftovers != NULL) /* XXX can't happen */ + con->leftovers[con->numLeftovers++] = origOptString; + continue; + } + + /* Make a copy we can hack at */ + { size_t bufsize = strlen(origOptString) + 1; + localOptString = optString = alloca(bufsize); + if (optString == NULL) /* XXX can't happen */ + return POPT_ERROR_BADOPT; + strlcpy(optString, origOptString, bufsize); + } + + if (optString[0] == '\0') + return POPT_ERROR_BADOPT; + + if (optString[1] == '-' && !optString[2]) { + con->restLeftover = 1; + continue; + } else { + char *oe; + int singleDash; + + optString++; + if (*optString == '-') + singleDash = 0, optString++; + else + singleDash = 1; + + /* XXX aliases with arg substitution need "--alias=arg" */ + if (handleAlias(con, optString, '\0', NULL)) + continue; + + if (handleExec(con, optString, '\0')) + continue; + + /* Check for "--long=arg" option. */ + for (oe = optString; *oe && *oe != '='; oe++) + {}; + if (*oe == '=') { + *oe++ = '\0'; + /* XXX longArg is mapped back to persistent storage. */ + longArg = origOptString + (oe - localOptString); + } else + oe = NULL; + + opt = findOption(con->options, optString, '\0', &cb, &cbData, + singleDash); + if (!opt && !singleDash) + return POPT_ERROR_BADOPT; + if (!opt && oe) + oe[-1] = '='; /* restore overwritten '=' */ + } + + if (!opt) { + con->os->nextCharArg = origOptString + 1; + longArg = NULL; + } else { + if (con->os == con->optionStack && + opt->argInfo & POPT_ARGFLAG_STRIP) + { + canstrip = 1; + poptStripArg(con, thisopt); + } + shorty = 0; + } + } + + /* Process next short option */ + /*@-branchstate@*/ /* FIX: W2DO? */ + if (con->os->nextCharArg) { + origOptString = con->os->nextCharArg; + + con->os->nextCharArg = NULL; + + if (handleAlias(con, NULL, *origOptString, origOptString + 1)) + continue; + + if (handleExec(con, NULL, *origOptString)) { + /* Restore rest of short options for further processing */ + origOptString++; + if (*origOptString != '\0') + con->os->nextCharArg = origOptString; + continue; + } + + opt = findOption(con->options, NULL, *origOptString, &cb, + &cbData, 0); + if (!opt) + return POPT_ERROR_BADOPT; + shorty = 1; + + origOptString++; + if (*origOptString != '\0') + con->os->nextCharArg = origOptString; + } + /*@=branchstate@*/ + + if (opt == NULL) return POPT_ERROR_BADOPT; /* XXX can't happen */ + if ((opt->argInfo & POPT_ARG_MASK) == POPT_ARG_NONE + || (opt->argInfo & POPT_ARG_MASK) == POPT_ARG_VAL) { + if (longArg || (con->os->nextCharArg && con->os->nextCharArg[0] == '=')) + return POPT_ERROR_UNWANTEDARG; + if (opt->arg) { + long val = (opt->argInfo & POPT_ARG_MASK) == POPT_ARG_VAL ? opt->val : 1; + if (poptSaveInt((int *)opt->arg, opt->argInfo, val)) + return POPT_ERROR_BADOPERATION; + } + } else { + con->os->nextArg = _free(con->os->nextArg); + /*@-usedef@*/ /* FIX: W2DO? */ + if (longArg) { + /*@=usedef@*/ + longArg = expandNextArg(con, longArg); + con->os->nextArg = longArg; + } else if (con->os->nextCharArg) { + longArg = expandNextArg(con, con->os->nextCharArg + (con->os->nextCharArg[0] == '=')); + con->os->nextArg = longArg; + con->os->nextCharArg = NULL; + } else { + while (con->os->next == con->os->argc && + con->os > con->optionStack) { + cleanOSE(con->os--); + } + if (con->os->next == con->os->argc) { + if (!(opt->argInfo & POPT_ARGFLAG_OPTIONAL)) + /*@-compdef@*/ /* FIX: con->os->argv not defined */ + return POPT_ERROR_NOARG; + /*@=compdef@*/ + con->os->nextArg = NULL; + } else { + + /* + * Make sure this isn't part of a short arg or the + * result of an alias expansion. + */ + if (con->os == con->optionStack && + (opt->argInfo & POPT_ARGFLAG_STRIP) && + canstrip) { + poptStripArg(con, con->os->next); + } + + if (con->os->argv != NULL) { /* XXX can't happen */ + /* XXX watchout: subtle side-effects live here. */ + longArg = con->os->argv[con->os->next++]; + longArg = expandNextArg(con, longArg); + con->os->nextArg = longArg; + } + } + } + longArg = NULL; + + if (opt->arg) { + switch (opt->argInfo & POPT_ARG_MASK) { + case POPT_ARG_STRING: + /* XXX memory leak, hard to plug */ + *((const char **) opt->arg) = (con->os->nextArg) + ? xstrdup(con->os->nextArg) : NULL; + /*@switchbreak@*/ break; + + case POPT_ARG_INT: + case POPT_ARG_LONG: + { long aLong = 0; + char *end; + + if (con->os->nextArg) { + aLong = strtol(con->os->nextArg, &end, 0); + if (!(end && *end == '\0')) + return POPT_ERROR_BADNUMBER; + } + + if ((opt->argInfo & POPT_ARG_MASK) == POPT_ARG_LONG) { + if (aLong == LONG_MIN || aLong == LONG_MAX) + return POPT_ERROR_OVERFLOW; + if (poptSaveLong((long *)opt->arg, opt->argInfo, aLong)) + return POPT_ERROR_BADOPERATION; + } else { + if (aLong > INT_MAX || aLong < INT_MIN) + return POPT_ERROR_OVERFLOW; + if (poptSaveInt((int *)opt->arg, opt->argInfo, aLong)) + return POPT_ERROR_BADOPERATION; + } + } /*@switchbreak@*/ break; + + case POPT_ARG_FLOAT: + case POPT_ARG_DOUBLE: + { double aDouble = 0.0; + char *end; + + if (con->os->nextArg) { + /*@-mods@*/ + int saveerrno = errno; + errno = 0; + aDouble = strtod(con->os->nextArg, &end); + if (errno == ERANGE) + return POPT_ERROR_OVERFLOW; + errno = saveerrno; + /*@=mods@*/ + if (*end != '\0') + return POPT_ERROR_BADNUMBER; + } + + if ((opt->argInfo & POPT_ARG_MASK) == POPT_ARG_DOUBLE) { + *((double *) opt->arg) = aDouble; + } else { +#define MY_ABS(a) ((((a) - 0.0) < DBL_EPSILON) ? -(a) : (a)) + if ((MY_ABS(aDouble) - FLT_MAX) > DBL_EPSILON) + return POPT_ERROR_OVERFLOW; + if ((FLT_MIN - MY_ABS(aDouble)) > DBL_EPSILON) + return POPT_ERROR_OVERFLOW; + *((float *) opt->arg) = aDouble; + } + } /*@switchbreak@*/ break; + default: + fprintf(stdout, + POPT_("option type (%d) not implemented in popt\n"), + (opt->argInfo & POPT_ARG_MASK)); + exit(EXIT_FAILURE); + /*@notreached@*/ /*@switchbreak@*/ break; + } + } + } + + if (cb) { + /*@-internalglobs@*/ + invokeCallbacksOPTION(con, con->options, opt, cbData, shorty); + /*@=internalglobs@*/ + } else if (opt->val && ((opt->argInfo & POPT_ARG_MASK) != POPT_ARG_VAL)) + done = 1; + + if ((con->finalArgvCount + 2) >= (con->finalArgvAlloced)) { + con->finalArgvAlloced += 10; + con->finalArgv = realloc(con->finalArgv, + sizeof(*con->finalArgv) * con->finalArgvAlloced); + } + + if (con->finalArgv != NULL) + { ssize_t bufsize = (opt->longName ? strlen(opt->longName) : 0) + 3; + char *s = malloc(bufsize); + if (s != NULL) { /* XXX can't happen */ + if (opt->longName) + snprintf(s, bufsize, "%s%s", + ((opt->argInfo & POPT_ARGFLAG_ONEDASH) ? "-" : "--"), + opt->longName); + else + snprintf(s, bufsize, "-%c", opt->shortName); + con->finalArgv[con->finalArgvCount++] = s; + } else + con->finalArgv[con->finalArgvCount++] = NULL; + } + + if (opt->arg && (opt->argInfo & POPT_ARG_MASK) == POPT_ARG_NONE) + /*@-ifempty@*/ ; /*@=ifempty@*/ + else if ((opt->argInfo & POPT_ARG_MASK) == POPT_ARG_VAL) + /*@-ifempty@*/ ; /*@=ifempty@*/ + else if ((opt->argInfo & POPT_ARG_MASK) != POPT_ARG_NONE) { + if (con->finalArgv != NULL && con->os->nextArg) + con->finalArgv[con->finalArgvCount++] = + /*@-nullpass@*/ /* LCL: con->os->nextArg != NULL */ + xstrdup(con->os->nextArg); + /*@=nullpass@*/ + } + } + + return (opt ? opt->val : -1); /* XXX can't happen */ +} +/*@=boundswrite@*/ + +const char * poptGetOptArg(poptContext con) +{ + const char * ret = NULL; + /*@-branchstate@*/ + if (con) { + ret = con->os->nextArg; + con->os->nextArg = NULL; + } + /*@=branchstate@*/ + return ret; +} + +const char * poptGetArg(poptContext con) +{ + const char * ret = NULL; + if (con && con->leftovers != NULL && con->nextLeftover < con->numLeftovers) + ret = con->leftovers[con->nextLeftover++]; + return ret; +} + +const char * poptPeekArg(poptContext con) +{ + const char * ret = NULL; + if (con && con->leftovers != NULL && con->nextLeftover < con->numLeftovers) + ret = con->leftovers[con->nextLeftover]; + return ret; +} + +/*@-boundswrite@*/ +const char ** poptGetArgs(poptContext con) +{ + if (con == NULL || + con->leftovers == NULL || con->numLeftovers == con->nextLeftover) + return NULL; + + /* some apps like [like RPM ;-) ] need this NULL terminated */ + con->leftovers[con->numLeftovers] = NULL; + + /*@-nullret -nullstate @*/ /* FIX: typedef double indirection. */ + return (con->leftovers + con->nextLeftover); + /*@=nullret =nullstate @*/ +} +/*@=boundswrite@*/ + +poptContext poptFreeContext(poptContext con) +{ + poptItem item; + int i; + + if (con == NULL) return con; + poptResetContext(con); + con->os->argb = _free(con->os->argb); + + if (con->aliases != NULL) + for (i = 0; i < con->numAliases; i++) { + item = con->aliases + i; + /*@-modobserver -observertrans -dependenttrans@*/ + item->option.longName = _free(item->option.longName); + item->option.descrip = _free(item->option.descrip); + item->option.argDescrip = _free(item->option.argDescrip); + /*@=modobserver =observertrans =dependenttrans@*/ + item->argv = _free(item->argv); + } + con->aliases = _free(con->aliases); + + if (con->execs != NULL) + for (i = 0; i < con->numExecs; i++) { + item = con->execs + i; + /*@-modobserver -observertrans -dependenttrans@*/ + item->option.longName = _free(item->option.longName); + item->option.descrip = _free(item->option.descrip); + item->option.argDescrip = _free(item->option.argDescrip); + /*@=modobserver =observertrans =dependenttrans@*/ + item->argv = _free(item->argv); + } + con->execs = _free(con->execs); + + con->leftovers = _free(con->leftovers); + con->finalArgv = _free(con->finalArgv); + con->appName = _free(con->appName); + con->otherHelp = _free(con->otherHelp); + con->execPath = _free(con->execPath); + con->arg_strip = PBM_FREE(con->arg_strip); + + con = _free(con); + return con; +} + +int poptAddAlias(poptContext con, struct poptAlias alias, + /*@unused@*/ UNUSED(int flags)) +{ + poptItem item = (poptItem) alloca(sizeof(*item)); + memset(item, 0, sizeof(*item)); + item->option.longName = alias.longName; + item->option.shortName = alias.shortName; + item->option.argInfo = POPT_ARGFLAG_DOC_HIDDEN; + item->option.arg = 0; + item->option.val = 0; + item->option.descrip = NULL; + item->option.argDescrip = NULL; + item->argc = alias.argc; + item->argv = alias.argv; + return poptAddItem(con, item, 0); +} + +/*@-boundswrite@*/ +/*@-mustmod@*/ /* LCL: con not modified? */ +int poptAddItem(poptContext con, poptItem newItem, int flags) +{ + poptItem * items, item; + int * nitems; + + switch (flags) { + case 1: + items = &con->execs; + nitems = &con->numExecs; + break; + case 0: + items = &con->aliases; + nitems = &con->numAliases; + break; + default: + return 1; + /*@notreached@*/ break; + } + + *items = realloc((*items), ((*nitems) + 1) * sizeof(**items)); + if ((*items) == NULL) + return 1; + + item = (*items) + (*nitems); + + item->option.longName = + (newItem->option.longName ? xstrdup(newItem->option.longName) : NULL); + item->option.shortName = newItem->option.shortName; + item->option.argInfo = newItem->option.argInfo; + item->option.arg = newItem->option.arg; + item->option.val = newItem->option.val; + item->option.descrip = + (newItem->option.descrip ? xstrdup(newItem->option.descrip) : NULL); + item->option.argDescrip = + (newItem->option.argDescrip ? xstrdup(newItem->option.argDescrip) : NULL); + item->argc = newItem->argc; + item->argv = newItem->argv; + + (*nitems)++; + + return 0; +} +/*@=mustmod@*/ +/*@=boundswrite@*/ + +const char * poptBadOption(poptContext con, int flags) +{ + struct optionStackEntry * os = NULL; + + if (con != NULL) + os = (flags & POPT_BADOPTION_NOALIAS) ? con->optionStack : con->os; + + /*@-nullderef@*/ /* LCL: os->argv != NULL */ + return (os && os->argv ? os->argv[os->next - 1] : NULL); + /*@=nullderef@*/ +} + +const char * poptStrerror(const int error) +{ + switch (error) { + case POPT_ERROR_NOARG: + return POPT_("missing argument"); + case POPT_ERROR_UNWANTEDARG: + return POPT_("option does not take an argument"); + case POPT_ERROR_BADOPT: + return POPT_("unknown option"); + case POPT_ERROR_BADOPERATION: + return POPT_("mutually exclusive logical operations requested"); + case POPT_ERROR_NULLARG: + return POPT_("opt->arg should not be NULL"); + case POPT_ERROR_OPTSTOODEEP: + return POPT_("aliases nested too deeply"); + case POPT_ERROR_BADQUOTE: + return POPT_("error in parameter quoting"); + case POPT_ERROR_BADNUMBER: + return POPT_("invalid numeric value"); + case POPT_ERROR_OVERFLOW: + return POPT_("number too large or too small"); + case POPT_ERROR_MALLOC: + return POPT_("memory allocation failed"); + case POPT_ERROR_ERRNO: + return strerror(errno); + default: + return POPT_("unknown error"); + } +} + +int poptStuffArgs(poptContext con, const char ** argv) +{ + int argc; + int rc; + + if ((con->os - con->optionStack) == POPT_OPTION_DEPTH) + return POPT_ERROR_OPTSTOODEEP; + + for (argc = 0; argv[argc]; argc++) + {}; + + con->os++; + con->os->next = 0; + con->os->nextArg = NULL; + con->os->nextCharArg = NULL; + con->os->currAlias = NULL; + rc = poptDupArgv(argc, argv, &con->os->argc, &con->os->argv); + con->os->argb = NULL; + con->os->stuffed = 1; + + return rc; +} + +const char * poptGetInvocationName(poptContext con) +{ + return (con->os->argv ? con->os->argv[0] : ""); +} + +/*@-boundswrite@*/ +int poptStrippedArgv(poptContext con, int argc, char ** argv) +{ + int numargs = argc; + int j = 1; + int i; + + /*@-sizeoftype@*/ + if (con->arg_strip) + for (i = 1; i < argc; i++) { + if (PBM_ISSET(i, con->arg_strip)) + numargs--; + } + + for (i = 1; i < argc; i++) { + if (con->arg_strip && PBM_ISSET(i, con->arg_strip)) + continue; + argv[j] = (j < numargs) ? argv[i] : NULL; + j++; + } + /*@=sizeoftype@*/ + + return numargs; +} +/*@=boundswrite@*/ diff --git a/rsync/popt/popt.h b/rsync/popt/popt.h new file mode 100644 index 0000000..8d85f73 --- /dev/null +++ b/rsync/popt/popt.h @@ -0,0 +1,565 @@ +/** \file popt/popt.h + * \ingroup popt + */ + +/* (C) 1998-2000 Red Hat, Inc. -- Licensing details are in the COPYING + file accompanying popt source distributions, available from + ftp://ftp.rpm.org/pub/rpm/dist. */ + +#ifndef H_POPT +#define H_POPT + +#include /* for FILE * */ + +#define POPT_OPTION_DEPTH 10 + +/** \ingroup popt + * \name Arg type identifiers + */ +/*@{*/ +#define POPT_ARG_NONE 0 /*!< no arg */ +#define POPT_ARG_STRING 1 /*!< arg will be saved as string */ +#define POPT_ARG_INT 2 /*!< arg will be converted to int */ +#define POPT_ARG_LONG 3 /*!< arg will be converted to long */ +#define POPT_ARG_INCLUDE_TABLE 4 /*!< arg points to table */ +#define POPT_ARG_CALLBACK 5 /*!< table-wide callback... must be + set first in table; arg points + to callback, descrip points to + callback data to pass */ +#define POPT_ARG_INTL_DOMAIN 6 /*!< set the translation domain + for this table and any + included tables; arg points + to the domain string */ +#define POPT_ARG_VAL 7 /*!< arg should take value val */ +#define POPT_ARG_FLOAT 8 /*!< arg will be converted to float */ +#define POPT_ARG_DOUBLE 9 /*!< arg will be converted to double */ + +#define POPT_ARG_MASK 0x0000FFFF +/*@}*/ + +/** \ingroup popt + * \name Arg modifiers + */ +/*@{*/ +#define POPT_ARGFLAG_ONEDASH 0x80000000 /*!< allow -longoption */ +#define POPT_ARGFLAG_DOC_HIDDEN 0x40000000 /*!< don't show in help/usage */ +#define POPT_ARGFLAG_STRIP 0x20000000 /*!< strip this arg from argv(only applies to long args) */ +#define POPT_ARGFLAG_OPTIONAL 0x10000000 /*!< arg may be missing */ + +#define POPT_ARGFLAG_OR 0x08000000 /*!< arg will be or'ed */ +#define POPT_ARGFLAG_NOR 0x09000000 /*!< arg will be nor'ed */ +#define POPT_ARGFLAG_AND 0x04000000 /*!< arg will be and'ed */ +#define POPT_ARGFLAG_NAND 0x05000000 /*!< arg will be nand'ed */ +#define POPT_ARGFLAG_XOR 0x02000000 /*!< arg will be xor'ed */ +#define POPT_ARGFLAG_NOT 0x01000000 /*!< arg will be negated */ +#define POPT_ARGFLAG_LOGICALOPS \ + (POPT_ARGFLAG_OR|POPT_ARGFLAG_AND|POPT_ARGFLAG_XOR) + +#define POPT_BIT_SET (POPT_ARG_VAL|POPT_ARGFLAG_OR) + /*!< set arg bit(s) */ +#define POPT_BIT_CLR (POPT_ARG_VAL|POPT_ARGFLAG_NAND) + /*!< clear arg bit(s) */ + +#define POPT_ARGFLAG_SHOW_DEFAULT 0x00800000 /*!< show default value in --help */ + +/*@}*/ + +/** \ingroup popt + * \name Callback modifiers + */ +/*@{*/ +#define POPT_CBFLAG_PRE 0x80000000 /*!< call the callback before parse */ +#define POPT_CBFLAG_POST 0x40000000 /*!< call the callback after parse */ +#define POPT_CBFLAG_INC_DATA 0x20000000 /*!< use data from the include line, + not the subtable */ +#define POPT_CBFLAG_SKIPOPTION 0x10000000 /*!< don't callback with option */ +#define POPT_CBFLAG_CONTINUE 0x08000000 /*!< continue callbacks with option */ +/*@}*/ + +/** \ingroup popt + * \name Error return values + */ +/*@{*/ +#define POPT_ERROR_NOARG -10 /*!< missing argument */ +#define POPT_ERROR_BADOPT -11 /*!< unknown option */ +#define POPT_ERROR_UNWANTEDARG -12 /*!< option does not take an argument */ +#define POPT_ERROR_OPTSTOODEEP -13 /*!< aliases nested too deeply */ +#define POPT_ERROR_BADQUOTE -15 /*!< error in paramter quoting */ +#define POPT_ERROR_ERRNO -16 /*!< errno set, use strerror(errno) */ +#define POPT_ERROR_BADNUMBER -17 /*!< invalid numeric value */ +#define POPT_ERROR_OVERFLOW -18 /*!< number too large or too small */ +#define POPT_ERROR_BADOPERATION -19 /*!< mutually exclusive logical operations requested */ +#define POPT_ERROR_NULLARG -20 /*!< opt->arg should not be NULL */ +#define POPT_ERROR_MALLOC -21 /*!< memory allocation failed */ +/*@}*/ + +/** \ingroup popt + * \name poptBadOption() flags + */ +/*@{*/ +#define POPT_BADOPTION_NOALIAS (1 << 0) /*!< don't go into an alias */ +/*@}*/ + +/** \ingroup popt + * \name poptGetContext() flags + */ +/*@{*/ +#define POPT_CONTEXT_NO_EXEC (1 << 0) /*!< ignore exec expansions */ +#define POPT_CONTEXT_KEEP_FIRST (1 << 1) /*!< pay attention to argv[0] */ +#define POPT_CONTEXT_POSIXMEHARDER (1 << 2) /*!< options can't follow args */ +#define POPT_CONTEXT_ARG_OPTS (1 << 4) /*!< return args as options with value 0 */ +/*@}*/ + +/** \ingroup popt + */ +struct poptOption { +/*@observer@*/ /*@null@*/ + const char * longName; /*!< may be NULL */ + char shortName; /*!< may be NUL */ + int argInfo; +/*@shared@*/ /*@null@*/ + void * arg; /*!< depends on argInfo */ + int val; /*!< 0 means don't return, just update flag */ +/*@observer@*/ /*@null@*/ + const char * descrip; /*!< description for autohelp -- may be NULL */ +/*@observer@*/ /*@null@*/ + const char * argDescrip; /*!< argument description for autohelp */ +}; + +/** \ingroup popt + * A popt alias argument for poptAddAlias(). + */ +struct poptAlias { +/*@owned@*/ /*@null@*/ + const char * longName; /*!< may be NULL */ + char shortName; /*!< may be NUL */ + int argc; +/*@owned@*/ + const char ** argv; /*!< must be free()able */ +}; + +/** \ingroup popt + * A popt alias or exec argument for poptAddItem(). + */ +/*@-exporttype@*/ +typedef struct poptItem_s { + struct poptOption option; /*!< alias/exec name(s) and description. */ + int argc; /*!< (alias) no. of args. */ +/*@owned@*/ + const char ** argv; /*!< (alias) args, must be free()able. */ +} * poptItem; +/*@=exporttype@*/ + +/** \ingroup popt + * \name Auto-generated help/usage + */ +/*@{*/ + +/** + * Empty table marker to enable displaying popt alias/exec options. + */ +/*@-exportvar@*/ +/*@unchecked@*/ /*@observer@*/ +extern struct poptOption poptAliasOptions[]; +/*@=exportvar@*/ +#define POPT_AUTOALIAS { NULL, '\0', POPT_ARG_INCLUDE_TABLE, poptAliasOptions, \ + 0, "Options implemented via popt alias/exec:", NULL }, + +/** + * Auto help table options. + */ +/*@-exportvar@*/ +/*@unchecked@*/ /*@observer@*/ +extern struct poptOption poptHelpOptions[]; +/*@=exportvar@*/ + +/*@-exportvar@*/ +/*@unchecked@*/ /*@observer@*/ +extern struct poptOption * poptHelpOptionsI18N; +/*@=exportvar@*/ + +#define POPT_AUTOHELP { NULL, '\0', POPT_ARG_INCLUDE_TABLE, poptHelpOptions, \ + 0, "Help options:", NULL }, + +#define POPT_TABLEEND { NULL, '\0', 0, 0, 0, NULL, NULL } +/*@}*/ + +/** \ingroup popt + */ +/*@-exporttype@*/ +typedef /*@abstract@*/ struct poptContext_s * poptContext; +/*@=exporttype@*/ + +/** \ingroup popt + */ +#ifndef __cplusplus +/*@-exporttype -typeuse@*/ +typedef struct poptOption * poptOption; +/*@=exporttype =typeuse@*/ +#endif + +/*@-exportconst@*/ +enum poptCallbackReason { + POPT_CALLBACK_REASON_PRE = 0, + POPT_CALLBACK_REASON_POST = 1, + POPT_CALLBACK_REASON_OPTION = 2 +}; +/*@=exportconst@*/ + +#ifdef __cplusplus +extern "C" { +#endif +/*@-type@*/ + +/** \ingroup popt + * Table callback prototype. + * @param con context + * @param reason reason for callback + * @param opt option that triggered callback + * @param arg @todo Document. + * @param data @todo Document. + */ +typedef void (*poptCallbackType) (poptContext con, + enum poptCallbackReason reason, + /*@null@*/ const struct poptOption * opt, + /*@null@*/ const char * arg, + /*@null@*/ const void * data) + /*@globals internalState @*/ + /*@modifies internalState @*/; + +/** \ingroup popt + * Initialize popt context. + * @param name context name (usually argv[0] program name) + * @param argc no. of arguments + * @param argv argument array + * @param options address of popt option table + * @param flags or'd POPT_CONTEXT_* bits + * @return initialized popt context + */ +/*@only@*/ /*@null@*/ +poptContext poptGetContext( + /*@dependent@*/ /*@keep@*/ const char * name, + int argc, /*@dependent@*/ /*@keep@*/ const char ** argv, + /*@dependent@*/ /*@keep@*/ const struct poptOption * options, + int flags) + /*@*/; + +/** \ingroup popt + * Reinitialize popt context. + * @param con context + */ +/*@unused@*/ +void poptResetContext(/*@null@*/poptContext con) + /*@modifies con @*/; + +/** \ingroup popt + * Return value of next option found. + * @param con context + * @return next option val, -1 on last item, POPT_ERROR_* on error + */ +int poptGetNextOpt(/*@null@*/poptContext con) + /*@globals fileSystem, internalState @*/ + /*@modifies con, fileSystem, internalState @*/; + +/** \ingroup popt + * Return next option argument (if any). + * @param con context + * @return option argument, NULL if no argument is available + */ +/*@observer@*/ /*@null@*/ /*@unused@*/ +const char * poptGetOptArg(/*@null@*/poptContext con) + /*@modifies con @*/; + +/** \ingroup popt + * Return next argument. + * @param con context + * @return next argument, NULL if no argument is available + */ +/*@observer@*/ /*@null@*/ /*@unused@*/ +const char * poptGetArg(/*@null@*/poptContext con) + /*@modifies con @*/; + +/** \ingroup popt + * Peek at current argument. + * @param con context + * @return current argument, NULL if no argument is available + */ +/*@observer@*/ /*@null@*/ /*@unused@*/ +const char * poptPeekArg(/*@null@*/poptContext con) + /*@*/; + +/** \ingroup popt + * Return remaining arguments. + * @param con context + * @return argument array, NULL terminated + */ +/*@observer@*/ /*@null@*/ +const char ** poptGetArgs(/*@null@*/poptContext con) + /*@modifies con @*/; + +/** \ingroup popt + * Return the option which caused the most recent error. + * @param con context + * @param flags + * @return offending option + */ +/*@observer@*/ +const char * poptBadOption(/*@null@*/poptContext con, int flags) + /*@*/; + +/** \ingroup popt + * Destroy context. + * @param con context + * @return NULL always + */ +/*@null@*/ +poptContext poptFreeContext( /*@only@*/ /*@null@*/ poptContext con) + /*@modifies con @*/; + +/** \ingroup popt + * Add arguments to context. + * @param con context + * @param argv argument array, NULL terminated + * @return 0 on success, POPT_ERROR_OPTSTOODEEP on failure + */ +/*@unused@*/ +int poptStuffArgs(poptContext con, /*@keep@*/ const char ** argv) + /*@modifies con @*/; + +/** \ingroup popt + * Add alias to context. + * @todo Pass alias by reference, not value. + * @deprecated Use poptAddItem instead. + * @param con context + * @param alias alias to add + * @param flags (unused) + * @return 0 on success + */ +/*@unused@*/ +int poptAddAlias(poptContext con, struct poptAlias alias, int flags) + /*@modifies con @*/; + +/** \ingroup popt + * Add alias/exec item to context. + * @param con context + * @param newItem alias/exec item to add + * @param flags 0 for alias, 1 for exec + * @return 0 on success + */ +int poptAddItem(poptContext con, poptItem newItem, int flags) + /*@modifies con @*/; + +/** \ingroup popt + * Read configuration file. + * @param con context + * @param fn file name to read + * @return 0 on success, POPT_ERROR_ERRNO on failure + */ +int poptReadConfigFile(poptContext con, const char * fn) + /*@globals errno, fileSystem, internalState @*/ + /*@modifies con->execs, con->numExecs, + errno, fileSystem, internalState @*/; + +/** \ingroup popt + * Read default configuration from /etc/popt and $HOME/.popt. + * @param con context + * @param useEnv (unused) + * @return 0 on success, POPT_ERROR_ERRNO on failure + */ +/*@unused@*/ +int poptReadDefaultConfig(poptContext con, /*@unused@*/ int useEnv) + /*@globals fileSystem, internalState @*/ + /*@modifies con->execs, con->numExecs, + fileSystem, internalState @*/; + +/** \ingroup popt + * Duplicate an argument array. + * @note: The argument array is malloc'd as a single area, so only argv must + * be free'd. + * + * @param argc no. of arguments + * @param argv argument array + * @retval argcPtr address of returned no. of arguments + * @retval argvPtr address of returned argument array + * @return 0 on success, POPT_ERROR_NOARG on failure + */ +int poptDupArgv(int argc, /*@null@*/ const char **argv, + /*@null@*/ /*@out@*/ int * argcPtr, + /*@null@*/ /*@out@*/ const char *** argvPtr) + /*@modifies *argcPtr, *argvPtr @*/; + +/** \ingroup popt + * Parse a string into an argument array. + * The parse allows ', ", and \ quoting, but ' is treated the same as " and + * both may include \ quotes. + * @note: The argument array is malloc'd as a single area, so only argv must + * be free'd. + * + * @param s string to parse + * @retval argcPtr address of returned no. of arguments + * @retval argvPtr address of returned argument array + */ +int poptParseArgvString(const char * s, + /*@out@*/ int * argcPtr, /*@out@*/ const char *** argvPtr) + /*@modifies *argcPtr, *argvPtr @*/; + +/** \ingroup popt + * Parses an input configuration file and returns an string that is a + * command line. For use with popt. You must free the return value when done. + * + * Given the file: +\verbatim +# this line is ignored + # this one too +aaa + bbb + ccc +bla=bla + +this_is = fdsafdas + bad_line= + reall bad line + reall bad line = again +5555= 55555 + test = with lots of spaces +\endverbatim +* +* The result is: +\verbatim +--aaa --bbb --ccc --bla="bla" --this_is="fdsafdas" --5555="55555" --test="with lots of spaces" +\endverbatim +* +* Passing this to poptParseArgvString() yields an argv of: +\verbatim +'--aaa' +'--bbb' +'--ccc' +'--bla=bla' +'--this_is=fdsafdas' +'--5555=55555' +'--test=with lots of spaces' +\endverbatim + * + * @bug NULL is returned if file line is too long. + * @bug Silently ignores invalid lines. + * + * @param fp file handle to read + * @param *argstrp return string of options (malloc'd) + * @param flags unused + * @return 0 on success + * @see poptParseArgvString + */ +/*@-fcnuse@*/ +int poptConfigFileToString(FILE *fp, /*@out@*/ char ** argstrp, int flags) + /*@globals fileSystem @*/ + /*@modifies *fp, *argstrp, fileSystem @*/; +/*@=fcnuse@*/ + +/** \ingroup popt + * Return formatted error string for popt failure. + * @param error popt error + * @return error string + */ +/*@observer@*/ +const char * poptStrerror(const int error) + /*@*/; + +/** \ingroup popt + * Limit search for executables. + * @param con context + * @param path single path to search for executables + * @param allowAbsolute absolute paths only? + */ +/*@unused@*/ +void poptSetExecPath(poptContext con, const char * path, int allowAbsolute) + /*@modifies con @*/; + +/** \ingroup popt + * Print detailed description of options. + * @param con context + * @param fp ouput file handle + * @param flags (unused) + */ +void poptPrintHelp(poptContext con, FILE * fp, /*@unused@*/ int flags) + /*@globals fileSystem @*/ + /*@modifies *fp, fileSystem @*/; + +/** \ingroup popt + * Print terse description of options. + * @param con context + * @param fp ouput file handle + * @param flags (unused) + */ +void poptPrintUsage(poptContext con, FILE * fp, /*@unused@*/ int flags) + /*@globals fileSystem @*/ + /*@modifies *fp, fileSystem @*/; + +/** \ingroup popt + * Provide text to replace default "[OPTION...]" in help/usage output. + * @param con context + * @param text replacement text + */ +/*@-fcnuse@*/ +void poptSetOtherOptionHelp(poptContext con, const char * text) + /*@modifies con @*/; +/*@=fcnuse@*/ + +/** \ingroup popt + * Return argv[0] from context. + * @param con context + * @return argv[0] + */ +/*@-fcnuse@*/ +/*@observer@*/ +const char * poptGetInvocationName(poptContext con) + /*@*/; +/*@=fcnuse@*/ + +/** \ingroup popt + * Shuffle argv pointers to remove stripped args, returns new argc. + * @param con context + * @param argc no. of args + * @param argv arg vector + * @return new argc + */ +/*@-fcnuse@*/ +int poptStrippedArgv(poptContext con, int argc, char ** argv) + /*@modifies *argv @*/; +/*@=fcnuse@*/ + +/** + * Save a long, performing logical operation with value. + * @warning Alignment check may be too strict on certain platorms. + * @param arg integer pointer, aligned on int boundary. + * @param argInfo logical operation (see POPT_ARGFLAG_*) + * @param aLong value to use + * @return 0 on success, POPT_ERROR_NULLARG/POPT_ERROR_BADOPERATION + */ +/*@-incondefs@*/ +/*@unused@*/ +int poptSaveLong(/*@null@*/ long * arg, int argInfo, long aLong) + /*@modifies *arg @*/ + /*@requires maxSet(arg) >= 0 /\ maxRead(arg) == 0 @*/; +/*@=incondefs@*/ + +/** + * Save an integer, performing logical operation with value. + * @warning Alignment check may be too strict on certain platorms. + * @param arg integer pointer, aligned on int boundary. + * @param argInfo logical operation (see POPT_ARGFLAG_*) + * @param aLong value to use + * @return 0 on success, POPT_ERROR_NULLARG/POPT_ERROR_BADOPERATION + */ +/*@-incondefs@*/ +/*@unused@*/ +int poptSaveInt(/*@null@*/ int * arg, int argInfo, long aLong) + /*@modifies *arg @*/ + /*@requires maxSet(arg) >= 0 /\ maxRead(arg) == 0 @*/; +/*@=incondefs@*/ + +/*@=type@*/ +#ifdef __cplusplus +} +#endif + +#endif diff --git a/rsync/popt/poptconfig.c b/rsync/popt/poptconfig.c new file mode 100644 index 0000000..9733d15 --- /dev/null +++ b/rsync/popt/poptconfig.c @@ -0,0 +1,183 @@ +/** \ingroup popt + * \file popt/poptconfig.c + */ + +/* (C) 1998-2002 Red Hat, Inc. -- Licensing details are in the COPYING + file accompanying popt source distributions, available from + ftp://ftp.rpm.org/pub/rpm/dist. */ + +#include "system.h" +#include "poptint.h" +/*@access poptContext @*/ + +/*@-compmempass@*/ /* FIX: item->option.longName kept, not dependent. */ +static void configLine(poptContext con, char * line) + /*@modifies con @*/ +{ + size_t nameLength; + const char * entryType; + const char * opt; + poptItem item = (poptItem) alloca(sizeof(*item)); + int i, j; + + if (con->appName == NULL) + return; + nameLength = strlen(con->appName); + +/*@-boundswrite@*/ + memset(item, 0, sizeof(*item)); + + if (strncmp(line, con->appName, nameLength)) return; + + line += nameLength; + if (*line == '\0' || !isSpace(line)) return; + + while (*line != '\0' && isSpace(line)) line++; + entryType = line; + while (*line == '\0' || !isSpace(line)) line++; + *line++ = '\0'; + + while (*line != '\0' && isSpace(line)) line++; + if (*line == '\0') return; + opt = line; + while (*line == '\0' || !isSpace(line)) line++; + *line++ = '\0'; + + while (*line != '\0' && isSpace(line)) line++; + if (*line == '\0') return; + + /*@-temptrans@*/ /* FIX: line alias is saved */ + if (opt[0] == '-' && opt[1] == '-') + item->option.longName = opt + 2; + else if (opt[0] == '-' && opt[2] == '\0') + item->option.shortName = opt[1]; + /*@=temptrans@*/ + + if (poptParseArgvString(line, &item->argc, &item->argv)) return; + + /*@-modobserver@*/ + item->option.argInfo = POPT_ARGFLAG_DOC_HIDDEN; + for (i = 0, j = 0; i < item->argc; i++, j++) { + const char * f; + if (!strncmp(item->argv[i], "--POPTdesc=", sizeof("--POPTdesc=")-1)) { + f = item->argv[i] + sizeof("--POPTdesc="); + if (f[0] == '$' && f[1] == '"') f++; + item->option.descrip = f; + item->option.argInfo &= ~POPT_ARGFLAG_DOC_HIDDEN; + j--; + } else + if (!strncmp(item->argv[i], "--POPTargs=", sizeof("--POPTargs=")-1)) { + f = item->argv[i] + sizeof("--POPTargs="); + if (f[0] == '$' && f[1] == '"') f++; + item->option.argDescrip = f; + item->option.argInfo &= ~POPT_ARGFLAG_DOC_HIDDEN; + item->option.argInfo |= POPT_ARG_STRING; + j--; + } else + if (j != i) + item->argv[j] = item->argv[i]; + } + if (j != i) { + item->argv[j] = NULL; + item->argc = j; + } + /*@=modobserver@*/ +/*@=boundswrite@*/ + + /*@-nullstate@*/ /* FIX: item->argv[] may be NULL */ + if (!strcmp(entryType, "alias")) + (void) poptAddItem(con, item, 0); + else if (!strcmp(entryType, "exec")) + (void) poptAddItem(con, item, 1); + /*@=nullstate@*/ +} +/*@=compmempass@*/ + +int poptReadConfigFile(poptContext con, const char * fn) +{ + const char * file, * chptr, * end; + char * buf; +/*@dependent@*/ char * dst; + int fd, rc; + off_t fileLength; + + fd = open(fn, O_RDONLY); + if (fd < 0) + return (errno == ENOENT ? 0 : POPT_ERROR_ERRNO); + + fileLength = lseek(fd, 0, SEEK_END); + if (fileLength == -1 || lseek(fd, 0, 0) == -1) { + rc = errno; + (void) close(fd); + errno = rc; + return POPT_ERROR_ERRNO; + } + + file = alloca(fileLength + 1); + if (read(fd, (char *)file, fileLength) != fileLength) { + rc = errno; + (void) close(fd); + errno = rc; + return POPT_ERROR_ERRNO; + } + if (close(fd) == -1) + return POPT_ERROR_ERRNO; + +/*@-boundswrite@*/ + dst = buf = alloca(fileLength + 1); + + chptr = file; + end = (file + fileLength); + /*@-infloops@*/ /* LCL: can't detect chptr++ */ + while (chptr < end) { + switch (*chptr) { + case '\n': + *dst = '\0'; + dst = buf; + while (*dst && isSpace(dst)) dst++; + if (*dst && *dst != '#') + configLine(con, dst); + chptr++; + /*@switchbreak@*/ break; + case '\\': + *dst++ = *chptr++; + if (chptr < end) { + if (*chptr == '\n') + dst--, chptr++; + /* \ at the end of a line does not insert a \n */ + else + *dst++ = *chptr++; + } + /*@switchbreak@*/ break; + default: + *dst++ = *chptr++; + /*@switchbreak@*/ break; + } + } + /*@=infloops@*/ +/*@=boundswrite@*/ + + return 0; +} + +int poptReadDefaultConfig(poptContext con, /*@unused@*/ UNUSED(int useEnv)) +{ + char * fn, * home; + int rc; + + if (con->appName == NULL) return 0; + + rc = poptReadConfigFile(con, "/etc/popt"); + if (rc) return rc; + + if ((home = getenv("HOME"))) { + size_t bufsize = strlen(home) + 20; + fn = alloca(bufsize); + if (fn == NULL) return 0; + snprintf(fn, bufsize, "%s/.popt", home); + rc = poptReadConfigFile(con, fn); + if (rc) return rc; + } + + return 0; +} diff --git a/rsync/popt/popthelp.c b/rsync/popt/popthelp.c new file mode 100644 index 0000000..6a00976 --- /dev/null +++ b/rsync/popt/popthelp.c @@ -0,0 +1,826 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 4 -*- */ + +/** \ingroup popt + * \file popt/popthelp.c + */ + +/* (C) 1998-2002 Red Hat, Inc. -- Licensing details are in the COPYING + file accompanying popt source distributions, available from + ftp://ftp.rpm.org/pub/rpm/dist. */ + +#include "system.h" + +/*#define POPT_WCHAR_HACK*/ +#ifdef POPT_WCHAR_HACK +#include /* for mbsrtowcs */ +/*@access mbstate_t @*/ +#endif +#include "poptint.h" + +/*@access poptContext@*/ + +/** + * Display arguments. + * @param con context + * @param foo (unused) + * @param key option(s) + * @param arg (unused) + * @param data (unused) + */ +static void displayArgs(poptContext con, + /*@unused@*/ UNUSED(enum poptCallbackReason foo), + struct poptOption * key, + /*@unused@*/ UNUSED(const char * arg), /*@unused@*/ UNUSED(void * data)) + /*@globals fileSystem@*/ + /*@modifies fileSystem@*/ +{ + if (key->shortName == '?') + poptPrintHelp(con, stdout, 0); + else + poptPrintUsage(con, stdout, 0); + exit(0); +} + +#ifdef NOTYET +/*@unchecked@*/ +static int show_option_defaults = 0; +#endif + +/** + * Empty table marker to enable displaying popt alias/exec options. + */ +/*@observer@*/ /*@unchecked@*/ +struct poptOption poptAliasOptions[] = { + POPT_TABLEEND +}; + +/** + * Auto help table options. + */ +/*@-castfcnptr@*/ +/*@observer@*/ /*@unchecked@*/ +struct poptOption poptHelpOptions[] = { + { NULL, '\0', POPT_ARG_CALLBACK, (void *)&displayArgs, '\0', NULL, NULL }, + { "help", '?', 0, NULL, '?', N_("Show this help message"), NULL }, + { "usage", '\0', 0, NULL, 'u', N_("Display brief usage message"), NULL }, + POPT_TABLEEND +} ; + +/*@observer@*/ /*@unchecked@*/ +static struct poptOption poptHelpOptions2[] = { +/*@-readonlytrans@*/ + { NULL, '\0', POPT_ARG_INTL_DOMAIN, PACKAGE, 0, NULL, NULL}, +/*@=readonlytrans@*/ + { NULL, '\0', POPT_ARG_CALLBACK, (void *)&displayArgs, '\0', NULL, NULL }, + { "help", '?', 0, NULL, '?', N_("Show this help message"), NULL }, + { "usage", '\0', 0, NULL, 'u', N_("Display brief usage message"), NULL }, +#ifdef NOTYET + { "defaults", '\0', POPT_ARG_NONE, &show_option_defaults, 0, + N_("Display option defaults in message"), NULL }, +#endif + POPT_TABLEEND +} ; + +/*@observer@*/ /*@unchecked@*/ +struct poptOption * poptHelpOptionsI18N = poptHelpOptions2; +/*@=castfcnptr@*/ + +/** + * @param table option(s) + */ +/*@observer@*/ /*@null@*/ static const char * +getTableTranslationDomain(/*@null@*/ const struct poptOption *table) + /*@*/ +{ + const struct poptOption *opt; + + if (table != NULL) + for (opt = table; opt->longName || opt->shortName || opt->arg; opt++) { + if (opt->argInfo == POPT_ARG_INTL_DOMAIN) + return opt->arg; + } + return NULL; +} + +/** + * @param opt option(s) + * @param translation_domain translation domain + */ +/*@observer@*/ /*@null@*/ static const char * +getArgDescrip(const struct poptOption * opt, + /*@-paramuse@*/ /* FIX: i18n macros disabled with lclint */ + /*@null@*/ UNUSED(const char * translation_domain)) + /*@=paramuse@*/ + /*@*/ +{ + if (!(opt->argInfo & POPT_ARG_MASK)) return NULL; + + if (opt == (poptHelpOptions + 1) || opt == (poptHelpOptions + 2)) + if (opt->argDescrip) return POPT_(opt->argDescrip); + + if (opt->argDescrip) return D_(translation_domain, opt->argDescrip); + + switch (opt->argInfo & POPT_ARG_MASK) { + /*case POPT_ARG_NONE: return POPT_("NONE");*/ /* impossible */ +#ifdef DYING + case POPT_ARG_VAL: return POPT_("VAL"); +#else + case POPT_ARG_VAL: return NULL; +#endif + case POPT_ARG_INT: return POPT_("INT"); + case POPT_ARG_LONG: return POPT_("LONG"); + case POPT_ARG_STRING: return POPT_("STRING"); + case POPT_ARG_FLOAT: return POPT_("FLOAT"); + case POPT_ARG_DOUBLE: return POPT_("DOUBLE"); + default: return POPT_("ARG"); + } +} + +/** + * Display default value for an option. + * @param lineLength display positions remaining + * @param opt option(s) + * @param translation_domain translation domain + * @return + */ +static /*@only@*/ /*@null@*/ char * +singleOptionDefaultValue(size_t lineLength, + const struct poptOption * opt, + /*@-paramuse@*/ /* FIX: i18n macros disabled with lclint */ + /*@null@*/ UNUSED(const char * translation_domain)) + /*@=paramuse@*/ + /*@*/ +{ + const char * defstr = D_(translation_domain, "default"); + size_t limit, bufsize = 4*lineLength + 1; + char * le = malloc(bufsize); + char * l = le; + + if (le == NULL) return NULL; /* XXX can't happen */ +/*@-boundswrite@*/ + *le++ = '('; + le += strlcpy(le, defstr, bufsize - 3); + *le++ = ':'; + *le++ = ' '; + limit = bufsize - (le - l) - 1; /* -1 for closing paren */ + if (opt->arg) /* XXX programmer error */ + switch (opt->argInfo & POPT_ARG_MASK) { + case POPT_ARG_VAL: + case POPT_ARG_INT: + { long aLong = *((int *)opt->arg); + le += snprintf(le, limit, "%ld", aLong); + } break; + case POPT_ARG_LONG: + { long aLong = *((long *)opt->arg); + le += snprintf(le, limit, "%ld", aLong); + } break; + case POPT_ARG_FLOAT: + { double aDouble = *((float *)opt->arg); + le += snprintf(le, limit, "%g", aDouble); + } break; + case POPT_ARG_DOUBLE: + { double aDouble = *((double *)opt->arg); + le += snprintf(le, limit, "%g", aDouble); + } break; + case POPT_ARG_STRING: + { const char * s = *(const char **)opt->arg; + if (s == NULL) { + le += strlcpy(le, "null", limit); + } else { + size_t len; + limit -= 2; /* make room for quotes */ + *le++ = '"'; + len = strlcpy(le, s, limit); + if (len >= limit) { + le += limit - 3 - 1; + *le++ = '.'; *le++ = '.'; *le++ = '.'; + } else + le += len; + *le++ = '"'; + } + } break; + case POPT_ARG_NONE: + default: + l = _free(l); + return NULL; + /*@notreached@*/ break; + } + *le++ = ')'; + *le = '\0'; +/*@=boundswrite@*/ + + return l; +} + +/** + * Display help text for an option. + * @param fp output file handle + * @param maxLeftCol largest argument display width + * @param opt option(s) + * @param translation_domain translation domain + */ +static void singleOptionHelp(FILE * fp, size_t maxLeftCol, + const struct poptOption * opt, + /*@null@*/ UNUSED(const char * translation_domain)) + /*@globals fileSystem @*/ + /*@modifies *fp, fileSystem @*/ +{ + size_t indentLength = maxLeftCol + 5; + size_t lineLength = 79 - indentLength; + const char * help = D_(translation_domain, opt->descrip); + const char * argDescrip = getArgDescrip(opt, translation_domain); + size_t helpLength; + char * defs = NULL; + char * left; + size_t lelen, limit; + size_t nb = maxLeftCol + 1; + int displaypad = 0; + + /* Make sure there's more than enough room in target buffer. */ + if (opt->longName) nb += strlen(opt->longName); + if (argDescrip) nb += strlen(argDescrip); + +/*@-boundswrite@*/ + left = malloc(nb); + if (left == NULL) return; /* XXX can't happen */ + left[0] = '\0'; + left[maxLeftCol] = '\0'; + + if (opt->longName && opt->shortName) + snprintf(left, nb, "-%c, %s%s", opt->shortName, + ((opt->argInfo & POPT_ARGFLAG_ONEDASH) ? "-" : "--"), + opt->longName); + else if (opt->shortName != '\0') + snprintf(left, nb, "-%c", opt->shortName); + else if (opt->longName) + snprintf(left, nb, "%s%s", + ((opt->argInfo & POPT_ARGFLAG_ONEDASH) ? "-" : "--"), + opt->longName); + if (!*left) goto out; + + if (argDescrip) { + char * le = left + strlen(left); + + if (opt->argInfo & POPT_ARGFLAG_OPTIONAL) + *le++ = '['; + + /* Choose type of output */ + /*@-branchstate@*/ + if (opt->argInfo & POPT_ARGFLAG_SHOW_DEFAULT) { + defs = singleOptionDefaultValue(lineLength, opt, translation_domain); + if (defs) { + size_t bufsize = (help ? strlen(help) : 0) + sizeof " " + strlen(defs); + char * t = malloc(bufsize); + if (t) { + snprintf(t, bufsize, "%s %s", help ? help : "", defs); + defs = _free(defs); + } + defs = t; + } + } + /*@=branchstate@*/ + + if (opt->argDescrip == NULL) { + switch (opt->argInfo & POPT_ARG_MASK) { + case POPT_ARG_NONE: + break; + case POPT_ARG_VAL: +#ifdef NOTNOW /* XXX pug ugly nerdy output */ + { long aLong = opt->val; + int ops = (opt->argInfo & POPT_ARGFLAG_LOGICALOPS); + int negate = (opt->argInfo & POPT_ARGFLAG_NOT); + + /* Don't bother displaying typical values */ + if (!ops && (aLong == 0L || aLong == 1L || aLong == -1L)) + break; + *le++ = '['; + switch (ops) { + case POPT_ARGFLAG_OR: + *le++ = '|'; + /*@innerbreak@*/ break; + case POPT_ARGFLAG_AND: + *le++ = '&'; + /*@innerbreak@*/ break; + case POPT_ARGFLAG_XOR: + *le++ = '^'; + /*@innerbreak@*/ break; + default: + /*@innerbreak@*/ break; + } + *le++ = (opt->longName != NULL ? '=' : ' '); + if (negate) *le++ = '~'; + /*@-formatconst@*/ + limit = nb - (le - left); + lelen = snprintf(le, limit, (ops ? "0x%lx" : "%ld"), aLong); + le += lelen >= limit ? limit - 1 : lelen; + /*@=formatconst@*/ + *le++ = ']'; + } +#endif + break; + case POPT_ARG_INT: + case POPT_ARG_LONG: + case POPT_ARG_FLOAT: + case POPT_ARG_DOUBLE: + case POPT_ARG_STRING: + *le++ = (opt->longName != NULL ? '=' : ' '); + limit = nb - (le - left); + lelen = strlcpy(le, argDescrip, limit); + le += lelen >= limit ? limit - 1 : lelen; + break; + default: + break; + } + } else { + + *le++ = '='; + limit = nb - (le - left); + lelen = strlcpy(le, argDescrip, limit); + if (lelen >= limit) + lelen = limit - 1; + le += lelen; + +#ifdef POPT_WCHAR_HACK + { const char * scopy = argDescrip; + mbstate_t t; + size_t n; + + memset ((void *)&t, '\0', sizeof (t)); /* In initial state. */ + /* Determine number of characters. */ + n = mbsrtowcs (NULL, &scopy, strlen(scopy), &t); + + displaypad = (int) (lelen-n); + } +#endif + } + if (opt->argInfo & POPT_ARGFLAG_OPTIONAL) + *le++ = ']'; + *le = '\0'; + } +/*@=boundswrite@*/ + + if (help) + fprintf(fp," %-*s ", (int)maxLeftCol+displaypad, left); + else { + fprintf(fp," %s\n", left); + goto out; + } + + left = _free(left); +/*@-branchstate@*/ + if (defs) { + help = defs; + defs = NULL; + } +/*@=branchstate@*/ + + helpLength = strlen(help); +/*@-boundsread@*/ + while (helpLength > lineLength) { + const char * ch; + char format[16]; + + ch = help + lineLength - 1; + while (ch > help && !isSpace(ch)) ch--; + if (ch == help) break; /* give up */ + while (ch > (help + 1) && isSpace(ch)) ch--; + ch++; + + snprintf(format, sizeof format, "%%.%ds\n%%%ds", (int) (ch - help), (int) indentLength); + /*@-formatconst@*/ + fprintf(fp, format, help, " "); + /*@=formatconst@*/ + help = ch; + while (isSpace(help) && *help) help++; + helpLength = strlen(help); + } +/*@=boundsread@*/ + + if (helpLength) fprintf(fp, "%s\n", help); + +out: + /*@-dependenttrans@*/ + defs = _free(defs); + /*@=dependenttrans@*/ + left = _free(left); +} + +/** + * Find display width for longest argument string. + * @param opt option(s) + * @param translation_domain translation domain + * @return display width + */ +static size_t maxArgWidth(const struct poptOption * opt, + /*@null@*/ UNUSED(const char * translation_domain)) + /*@*/ +{ + size_t max = 0; + size_t len = 0; + const char * s; + + if (opt != NULL) + while (opt->longName || opt->shortName || opt->arg) { + if ((opt->argInfo & POPT_ARG_MASK) == POPT_ARG_INCLUDE_TABLE) { + if (opt->arg) /* XXX program error */ + len = maxArgWidth(opt->arg, translation_domain); + if (len > max) max = len; + } else if (!(opt->argInfo & POPT_ARGFLAG_DOC_HIDDEN)) { + len = sizeof(" ")-1; + if (opt->shortName != '\0') len += sizeof("-X")-1; + if (opt->shortName != '\0' && opt->longName) len += sizeof(", ")-1; + if (opt->longName) { + len += ((opt->argInfo & POPT_ARGFLAG_ONEDASH) + ? sizeof("-")-1 : sizeof("--")-1); + len += strlen(opt->longName); + } + + s = getArgDescrip(opt, translation_domain); + +#ifdef POPT_WCHAR_HACK + /* XXX Calculate no. of display characters. */ + if (s) { + const char * scopy = s; + mbstate_t t; + size_t n; + +/*@-boundswrite@*/ + memset ((void *)&t, '\0', sizeof (t)); /* In initial state. */ +/*@=boundswrite@*/ + /* Determine number of characters. */ + n = mbsrtowcs (NULL, &scopy, strlen(scopy), &t); + len += sizeof("=")-1 + n; + } +#else + if (s) + len += sizeof("=")-1 + strlen(s); +#endif + + if (opt->argInfo & POPT_ARGFLAG_OPTIONAL) len += sizeof("[]")-1; + if (len > max) max = len; + } + + opt++; + } + + return max; +} + +/** + * Display popt alias and exec help. + * @param fp output file handle + * @param items alias/exec array + * @param nitems no. of alias/exec entries + * @param left largest argument display width + * @param translation_domain translation domain + */ +static void itemHelp(FILE * fp, + /*@null@*/ poptItem items, int nitems, size_t left, + /*@null@*/ UNUSED(const char * translation_domain)) + /*@globals fileSystem @*/ + /*@modifies *fp, fileSystem @*/ +{ + poptItem item; + int i; + + if (items != NULL) + for (i = 0, item = items; i < nitems; i++, item++) { + const struct poptOption * opt; + opt = &item->option; + if ((opt->longName || opt->shortName) && + !(opt->argInfo & POPT_ARGFLAG_DOC_HIDDEN)) + singleOptionHelp(fp, left, opt, translation_domain); + } +} + +/** + * Display help text for a table of options. + * @param con context + * @param fp output file handle + * @param table option(s) + * @param left largest argument display width + * @param translation_domain translation domain + */ +static void singleTableHelp(poptContext con, FILE * fp, + /*@null@*/ const struct poptOption * table, size_t left, + /*@null@*/ UNUSED(const char * translation_domain)) + /*@globals fileSystem @*/ + /*@modifies *fp, fileSystem @*/ +{ + const struct poptOption * opt; + const char *sub_transdom; + + if (table == poptAliasOptions) { + itemHelp(fp, con->aliases, con->numAliases, left, NULL); + itemHelp(fp, con->execs, con->numExecs, left, NULL); + return; + } + + if (table != NULL) + for (opt = table; (opt->longName || opt->shortName || opt->arg); opt++) { + if ((opt->longName || opt->shortName) && + !(opt->argInfo & POPT_ARGFLAG_DOC_HIDDEN)) + singleOptionHelp(fp, left, opt, translation_domain); + } + + if (table != NULL) + for (opt = table; (opt->longName || opt->shortName || opt->arg); opt++) { + if ((opt->argInfo & POPT_ARG_MASK) != POPT_ARG_INCLUDE_TABLE) + continue; + sub_transdom = getTableTranslationDomain(opt->arg); + if (sub_transdom == NULL) + sub_transdom = translation_domain; + + if (opt->descrip) + fprintf(fp, "\n%s\n", D_(sub_transdom, opt->descrip)); + + singleTableHelp(con, fp, opt->arg, left, sub_transdom); + } +} + +/** + * @param con context + * @param fp output file handle + */ +static int showHelpIntro(poptContext con, FILE * fp) + /*@globals fileSystem @*/ + /*@modifies *fp, fileSystem @*/ +{ + int len = 6; + const char * fn; + + fprintf(fp, POPT_("Usage:")); + if (!(con->flags & POPT_CONTEXT_KEEP_FIRST)) { +/*@-boundsread@*/ + /*@-nullderef -type@*/ /* LCL: wazzup? */ + fn = con->optionStack->argv[0]; + /*@=nullderef =type@*/ +/*@=boundsread@*/ + if (fn == NULL) return len; + if (strchr(fn, '/')) fn = strrchr(fn, '/') + 1; + fprintf(fp, " %s", fn); + len += strlen(fn) + 1; + } + + return len; +} + +void poptPrintHelp(poptContext con, FILE * fp, /*@unused@*/ UNUSED(int flags)) +{ + size_t leftColWidth; + + (void) showHelpIntro(con, fp); + if (con->otherHelp) + fprintf(fp, " %s\n", con->otherHelp); + else + fprintf(fp, " %s\n", POPT_("[OPTION...]")); + + leftColWidth = maxArgWidth(con->options, NULL); + singleTableHelp(con, fp, con->options, leftColWidth, NULL); +} + +/** + * Display usage text for an option. + * @param fp output file handle + * @param cursor current display position + * @param opt option(s) + * @param translation_domain translation domain + */ +static size_t singleOptionUsage(FILE * fp, size_t cursor, + const struct poptOption * opt, + /*@null@*/ const char *translation_domain) + /*@globals fileSystem @*/ + /*@modifies *fp, fileSystem @*/ +{ + size_t len = 4; + char shortStr[2] = { '\0', '\0' }; + const char * item = shortStr; + const char * argDescrip = getArgDescrip(opt, translation_domain); + + if (opt->shortName != '\0' && opt->longName != NULL) { + len += 2; + if (!(opt->argInfo & POPT_ARGFLAG_ONEDASH)) len++; + len += strlen(opt->longName); + } else if (opt->shortName != '\0') { + len++; + shortStr[0] = opt->shortName; + shortStr[1] = '\0'; + } else if (opt->longName) { + len += strlen(opt->longName); + if (!(opt->argInfo & POPT_ARGFLAG_ONEDASH)) len++; + item = opt->longName; + } + + if (len == 4) return cursor; + +#ifdef POPT_WCHAR_HACK + /* XXX Calculate no. of display characters. */ + if (argDescrip) { + const char * scopy = argDescrip; + mbstate_t t; + size_t n; + +/*@-boundswrite@*/ + memset ((void *)&t, '\0', sizeof (t)); /* In initial state. */ +/*@=boundswrite@*/ + /* Determine number of characters. */ + n = mbsrtowcs (NULL, &scopy, strlen(scopy), &t); + len += sizeof("=")-1 + n; + } +#else + if (argDescrip) + len += sizeof("=")-1 + strlen(argDescrip); +#endif + + if ((cursor + len) > 79) { + fprintf(fp, "\n "); + cursor = 7; + } + + if (opt->longName && opt->shortName) { + fprintf(fp, " [-%c|-%s%s%s%s]", + opt->shortName, ((opt->argInfo & POPT_ARGFLAG_ONEDASH) ? "" : "-"), + opt->longName, + (argDescrip ? " " : ""), + (argDescrip ? argDescrip : "")); + } else { + fprintf(fp, " [-%s%s%s%s]", + ((opt->shortName || (opt->argInfo & POPT_ARGFLAG_ONEDASH)) ? "" : "-"), + item, + (argDescrip ? (opt->shortName != '\0' ? " " : "=") : ""), + (argDescrip ? argDescrip : "")); + } + + return cursor + len + 1; +} + +/** + * Display popt alias and exec usage. + * @param fp output file handle + * @param cursor current display position + * @param item alias/exec array + * @param nitems no. of ara/exec entries + * @param translation_domain translation domain + */ +static size_t itemUsage(FILE * fp, size_t cursor, + /*@null@*/ poptItem item, int nitems, + /*@null@*/ UNUSED(const char * translation_domain)) + /*@globals fileSystem @*/ + /*@modifies *fp, fileSystem @*/ +{ + int i; + + /*@-branchstate@*/ /* FIX: W2DO? */ + if (item != NULL) + for (i = 0; i < nitems; i++, item++) { + const struct poptOption * opt; + opt = &item->option; + if ((opt->argInfo & POPT_ARG_MASK) == POPT_ARG_INTL_DOMAIN) { + translation_domain = (const char *)opt->arg; + } else if ((opt->longName || opt->shortName) && + !(opt->argInfo & POPT_ARGFLAG_DOC_HIDDEN)) { + cursor = singleOptionUsage(fp, cursor, opt, translation_domain); + } + } + /*@=branchstate@*/ + + return cursor; +} + +/** + * Keep track of option tables already processed. + */ +typedef struct poptDone_s { + int nopts; + int maxopts; + const void ** opts; +} * poptDone; + +/** + * Display usage text for a table of options. + * @param con context + * @param fp output file handle + * @param cursor current display position + * @param opt option(s) + * @param translation_domain translation domain + * @param done tables already processed + * @return + */ +static size_t singleTableUsage(poptContext con, FILE * fp, size_t cursor, + /*@null@*/ const struct poptOption * opt, + /*@null@*/ UNUSED(const char * translation_domain), + /*@null@*/ poptDone done) + /*@globals fileSystem @*/ + /*@modifies *fp, done, fileSystem @*/ +{ + /*@-branchstate@*/ /* FIX: W2DO? */ + if (opt != NULL) + for (; (opt->longName || opt->shortName || opt->arg) ; opt++) { + if ((opt->argInfo & POPT_ARG_MASK) == POPT_ARG_INTL_DOMAIN) { + translation_domain = (const char *)opt->arg; + } else if ((opt->argInfo & POPT_ARG_MASK) == POPT_ARG_INCLUDE_TABLE) { + if (done) { + int i = 0; + for (i = 0; i < done->nopts; i++) { +/*@-boundsread@*/ + const void * that = done->opts[i]; +/*@=boundsread@*/ + if (that == NULL || that != opt->arg) + /*@innercontinue@*/ continue; + /*@innerbreak@*/ break; + } + /* Skip if this table has already been processed. */ + if (opt->arg == NULL || i < done->nopts) + continue; +/*@-boundswrite@*/ + if (done->nopts < done->maxopts) + done->opts[done->nopts++] = (const void *) opt->arg; +/*@=boundswrite@*/ + } + cursor = singleTableUsage(con, fp, cursor, opt->arg, + translation_domain, done); + } else if ((opt->longName || opt->shortName) && + !(opt->argInfo & POPT_ARGFLAG_DOC_HIDDEN)) { + cursor = singleOptionUsage(fp, cursor, opt, translation_domain); + } + } + /*@=branchstate@*/ + + return cursor; +} + +/** + * Return concatenated short options for display. + * @todo Sub-tables should be recursed. + * @param opt option(s) + * @param fp output file handle + * @retval str concatenation of short options + * @return length of display string + */ +static int showShortOptions(const struct poptOption * opt, FILE * fp, + /*@null@*/ char * str) + /*@globals fileSystem @*/ + /*@modifies *str, *fp, fileSystem @*/ + /*@requires maxRead(str) >= 0 @*/ +{ + /* bufsize larger then the ascii set, lazy alloca on top level call. */ + char * s = (str != NULL ? str : memset(alloca(300), 0, 300)); + int len = 0; + + if (s == NULL) + return 0; + +/*@-boundswrite@*/ + if (opt != NULL) + for (; (opt->longName || opt->shortName || opt->arg); opt++) { + if (opt->shortName && !(opt->argInfo & POPT_ARG_MASK)) + s[strlen(s)] = opt->shortName; + else if ((opt->argInfo & POPT_ARG_MASK) == POPT_ARG_INCLUDE_TABLE) + if (opt->arg) /* XXX program error */ + len = showShortOptions(opt->arg, fp, s); + } +/*@=boundswrite@*/ + + /* On return to top level, print the short options, return print length. */ + if (s == str && *s != '\0') { + fprintf(fp, " [-%s]", s); + len = strlen(s) + sizeof(" [-]")-1; + } + return len; +} + +void poptPrintUsage(poptContext con, FILE * fp, /*@unused@*/ UNUSED(int flags)) +{ + poptDone done = memset(alloca(sizeof(*done)), 0, sizeof(*done)); + size_t cursor; + + done->nopts = 0; + done->maxopts = 64; + cursor = done->maxopts * sizeof(*done->opts); +/*@-boundswrite@*/ + done->opts = memset(alloca(cursor), 0, cursor); + /*@-keeptrans@*/ + done->opts[done->nopts++] = (const void *) con->options; + /*@=keeptrans@*/ +/*@=boundswrite@*/ + + cursor = showHelpIntro(con, fp); + cursor += showShortOptions(con->options, fp, NULL); + cursor = singleTableUsage(con, fp, cursor, con->options, NULL, done); + cursor = itemUsage(fp, cursor, con->aliases, con->numAliases, NULL); + cursor = itemUsage(fp, cursor, con->execs, con->numExecs, NULL); + + if (con->otherHelp) { + cursor += strlen(con->otherHelp) + 1; + if (cursor > 79) fprintf(fp, "\n "); + fprintf(fp, " %s", con->otherHelp); + } + + fprintf(fp, "\n"); +} + +void poptSetOtherOptionHelp(poptContext con, const char * text) +{ + con->otherHelp = _free(con->otherHelp); + con->otherHelp = xstrdup(text); +} diff --git a/rsync/popt/poptint.h b/rsync/popt/poptint.h new file mode 100644 index 0000000..bec7c97 --- /dev/null +++ b/rsync/popt/poptint.h @@ -0,0 +1,122 @@ +/** \ingroup popt + * \file popt/poptint.h + */ + +/* (C) 1998-2000 Red Hat, Inc. -- Licensing details are in the COPYING + file accompanying popt source distributions, available from + ftp://ftp.rpm.org/pub/rpm/dist. */ + +#ifndef H_POPTINT +#define H_POPTINT + +/** + * Wrapper to free(3), hides const compilation noise, permit NULL, return NULL. + * @param p memory to free + * @retval NULL always + */ +/*@unused@*/ static inline /*@null@*/ void * +_free(/*@only@*/ /*@null@*/ const void * p) + /*@modifies p @*/ +{ + if (p != NULL) free((void *)p); + return NULL; +} + +static inline int +isSpace(const char *ptr) +{ + return isspace(*(unsigned char *)ptr); +} + +/* Bit mask macros. */ +/*@-exporttype -redef @*/ +typedef unsigned int __pbm_bits; +/*@=exporttype =redef @*/ +#define __PBM_NBITS (8 * sizeof (__pbm_bits)) +#define __PBM_IX(d) ((d) / __PBM_NBITS) +#define __PBM_MASK(d) ((__pbm_bits) 1 << (((unsigned)(d)) % __PBM_NBITS)) +/*@-exporttype -redef @*/ +typedef struct { + __pbm_bits bits[1]; +} pbm_set; +/*@=exporttype =redef @*/ +#define __PBM_BITS(set) ((set)->bits) + +#define PBM_ALLOC(d) calloc(__PBM_IX (d) + 1, sizeof(__pbm_bits)) +#define PBM_FREE(s) _free(s); +#define PBM_SET(d, s) (__PBM_BITS (s)[__PBM_IX (d)] |= __PBM_MASK (d)) +#define PBM_CLR(d, s) (__PBM_BITS (s)[__PBM_IX (d)] &= ~__PBM_MASK (d)) +#define PBM_ISSET(d, s) ((__PBM_BITS (s)[__PBM_IX (d)] & __PBM_MASK (d)) != 0) + +struct optionStackEntry { + int argc; +/*@only@*/ /*@null@*/ + const char ** argv; +/*@only@*/ /*@null@*/ + pbm_set * argb; + int next; +/*@only@*/ /*@null@*/ + const char * nextArg; +/*@observer@*/ /*@null@*/ + const char * nextCharArg; +/*@dependent@*/ /*@null@*/ + poptItem currAlias; + int stuffed; +}; + +struct poptContext_s { + struct optionStackEntry optionStack[POPT_OPTION_DEPTH]; +/*@dependent@*/ + struct optionStackEntry * os; +/*@owned@*/ /*@null@*/ + const char ** leftovers; + int numLeftovers; + int nextLeftover; +/*@keep@*/ + const struct poptOption * options; + int restLeftover; +/*@only@*/ /*@null@*/ + const char * appName; +/*@only@*/ /*@null@*/ + poptItem aliases; + int numAliases; + int flags; +/*@owned@*/ /*@null@*/ + poptItem execs; + int numExecs; +/*@only@*/ /*@null@*/ + const char ** finalArgv; + int finalArgvCount; + int finalArgvAlloced; +/*@dependent@*/ /*@null@*/ + poptItem doExec; +/*@only@*/ + const char * execPath; + int execAbsolute; +/*@only@*/ /*@relnull@*/ + const char * otherHelp; +/*@null@*/ + pbm_set * arg_strip; +}; + +#ifdef HAVE_LIBINTL_H +#include +#endif + +#if defined(HAVE_GETTEXT) && !defined(__LCLINT__) +#define _(foo) gettext(foo) +#else +#define _(foo) foo +#endif + +#if defined(HAVE_DCGETTEXT) && !defined(__LCLINT__) +#define D_(dom, str) dgettext(dom, str) +#define POPT_(foo) D_("popt", foo) +#else +#define D_(dom, str) str +#define POPT_(foo) foo +#endif + +#define N_(foo) foo + +#endif diff --git a/rsync/popt/poptparse.c b/rsync/popt/poptparse.c new file mode 100644 index 0000000..e003a04 --- /dev/null +++ b/rsync/popt/poptparse.c @@ -0,0 +1,231 @@ +/** \ingroup popt + * \file popt/poptparse.c + */ + +/* (C) 1998-2002 Red Hat, Inc. -- Licensing details are in the COPYING + file accompanying popt source distributions, available from + ftp://ftp.rpm.org/pub/rpm/dist. */ + +#include "system.h" + +#include "poptint.h" + +#define POPT_ARGV_ARRAY_GROW_DELTA 5 + +/*@-boundswrite@*/ +int poptDupArgv(int argc, const char **argv, + int * argcPtr, const char *** argvPtr) +{ + size_t nb = (argc + 1) * sizeof(*argv); + const char ** argv2; + char * dst; + int i; + + if (argc <= 0 || argv == NULL) /* XXX can't happen */ + return POPT_ERROR_NOARG; + for (i = 0; i < argc; i++) { + if (argv[i] == NULL) + return POPT_ERROR_NOARG; + nb += strlen(argv[i]) + 1; + } + + dst = malloc(nb); + if (dst == NULL) /* XXX can't happen */ + return POPT_ERROR_MALLOC; + argv2 = (void *) dst; + dst += (argc + 1) * sizeof(*argv); + + /*@-branchstate@*/ + for (i = 0; i < argc; i++) { + argv2[i] = dst; + dst += strlcpy(dst, argv[i], nb) + 1; + } + /*@=branchstate@*/ + argv2[argc] = NULL; + + if (argvPtr) { + *argvPtr = argv2; + } else { + free(argv2); + argv2 = NULL; + } + if (argcPtr) + *argcPtr = argc; + return 0; +} +/*@=boundswrite@*/ + +/*@-bounds@*/ +int poptParseArgvString(const char * s, int * argcPtr, const char *** argvPtr) +{ + const char * src; + char quote = '\0'; + int argvAlloced = POPT_ARGV_ARRAY_GROW_DELTA; + const char ** argv = malloc(sizeof(*argv) * argvAlloced); + int argc = 0; + int buflen = strlen(s) + 1; + char * buf = memset(alloca(buflen), 0, buflen); + int rc = POPT_ERROR_MALLOC; + + if (argv == NULL) return rc; + argv[argc] = buf; + + for (src = s; *src != '\0'; src++) { + if (quote == *src) { + quote = '\0'; + } else if (quote != '\0') { + if (*src == '\\') { + src++; + if (!*src) { + rc = POPT_ERROR_BADQUOTE; + goto exit; + } + if (*src != quote) *buf++ = '\\'; + } + *buf++ = *src; + } else if (isSpace(src)) { + if (*argv[argc] != '\0') { + buf++, argc++; + if (argc == argvAlloced) { + argvAlloced += POPT_ARGV_ARRAY_GROW_DELTA; + argv = realloc(argv, sizeof(*argv) * argvAlloced); + if (argv == NULL) goto exit; + } + argv[argc] = buf; + } + } else switch (*src) { + case '"': + case '\'': + quote = *src; + /*@switchbreak@*/ break; + case '\\': + src++; + if (!*src) { + rc = POPT_ERROR_BADQUOTE; + goto exit; + } + /*@fallthrough@*/ + default: + *buf++ = *src; + /*@switchbreak@*/ break; + } + } + + if (strlen(argv[argc])) { + argc++, buf++; + } + + rc = poptDupArgv(argc, argv, argcPtr, argvPtr); + +exit: + if (argv) free(argv); + return rc; +} +/*@=bounds@*/ + +/* still in the dev stage. + * return values, perhaps 1== file erro + * 2== line to long + * 3== umm.... more? + */ +int poptConfigFileToString(FILE *fp, char ** argstrp, /*@unused@*/ UNUSED(int flags)) +{ + char line[999]; + char * argstr; + char * p; + char * q; + char * x; + int t; + int argvlen = 0; + size_t maxlinelen = sizeof(line); + size_t linelen; + int maxargvlen = 480; + int linenum = 0; + + *argstrp = NULL; + + /* | this_is = our_line + * p q x + */ + + if (fp == NULL) + return POPT_ERROR_NULLARG; + + argstr = calloc(maxargvlen, sizeof(*argstr)); + if (argstr == NULL) return POPT_ERROR_MALLOC; + + while (fgets(line, (int)maxlinelen, fp) != NULL) { + linenum++; + p = line; + + /* loop until first non-space char or EOL */ + while( *p != '\0' && isSpace(p) ) + p++; + + linelen = strlen(p); + if (linelen >= maxlinelen-1) { + free(argstr); + return POPT_ERROR_OVERFLOW; /* XXX line too long */ + } + + if (*p == '\0' || *p == '\n') continue; /* line is empty */ + if (*p == '#') continue; /* comment line */ + + q = p; + + while (*q != '\0' && (!isSpace(q)) && *q != '=') + q++; + + if (isSpace(q)) { + /* a space after the name, find next non space */ + *q++='\0'; + while( *q != '\0' && isSpace(q) ) q++; + } + if (*q == '\0') { + /* single command line option (ie, no name=val, just name) */ + q[-1] = '\0'; /* kill off newline from fgets() call */ + argvlen += (t = q - p) + (sizeof(" --")-1); + if (argvlen >= maxargvlen) { + maxargvlen = (t > maxargvlen) ? t*2 : maxargvlen*2; + argstr = realloc(argstr, maxargvlen); + if (argstr == NULL) return POPT_ERROR_MALLOC; + } + strlcat(argstr, " --", maxargvlen); + strlcat(argstr, p, maxargvlen); + continue; + } + if (*q != '=') + continue; /* XXX for now, silently ignore bogus line */ + + /* *q is an equal sign. */ + *q++ = '\0'; + + /* find next non-space letter of value */ + while (*q != '\0' && isSpace(q)) + q++; + if (*q == '\0') + continue; /* XXX silently ignore missing value */ + + /* now, loop and strip all ending whitespace */ + x = p + linelen; + while (isSpace(--x)) + *x = 0; /* null out last char if space (including fgets() NL) */ + + /* rest of line accept */ + t = x - p; + argvlen += t + (sizeof("' --='")-1); + if (argvlen >= maxargvlen) { + maxargvlen = (t > maxargvlen) ? t*2 : maxargvlen*2; + argstr = realloc(argstr, maxargvlen); + if (argstr == NULL) return POPT_ERROR_MALLOC; + } + strlcat(argstr, " --", maxargvlen); + strlcat(argstr, p, maxargvlen); + strlcat(argstr, "=\"", maxargvlen); + strlcat(argstr, q, maxargvlen); + strlcat(argstr, "\"", maxargvlen); + } + + *argstrp = argstr; + return 0; +} diff --git a/rsync/popt/system.h b/rsync/popt/system.h new file mode 100644 index 0000000..50cecaf --- /dev/null +++ b/rsync/popt/system.h @@ -0,0 +1,130 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#if defined (__GLIBC__) && defined(__LCLINT__) +/*@-declundef@*/ +/*@unchecked@*/ +extern __const __int32_t *__ctype_tolower; +/*@unchecked@*/ +extern __const __int32_t *__ctype_toupper; +/*@=declundef@*/ +#endif + +#include + +#include +#include +#include + +#if HAVE_MCHECK_H +#include +#endif + +#include +#ifdef HAVE_SYS_TYPES_H +# include +#endif +#ifdef STDC_HEADERS +# include +# include +#else +# ifdef HAVE_STDLIB_H +# include +# endif +#endif +#ifdef HAVE_STRING_H +# if !defined STDC_HEADERS && defined HAVE_MEMORY_H +# include +# endif +# include +#endif +#ifdef HAVE_STRINGS_H +# include +#endif +#ifdef HAVE_UNISTD_H +# include +#endif + +#ifndef __GNUC__ +#define __attribute__(x) +#endif + +#ifdef __NeXT +/* access macros are not declared in non posix mode in unistd.h - + don't try to use posix on NeXTstep 3.3 ! */ +#include +#endif + +#if defined(__LCLINT__) +/*@-declundef -incondefs @*/ /* LCL: missing annotation */ +/*@only@*/ /*@out@*/ +void * alloca (size_t __size) + /*@ensures MaxSet(result) == (__size - 1) @*/ + /*@*/; +/*@=declundef =incondefs @*/ +#endif + +/* AIX requires this to be the first thing in the file. */ +#ifndef __GNUC__ +# if HAVE_ALLOCA_H +# include +# else +# ifdef _AIX +#pragma alloca +# else +# ifdef HAVE_ALLOCA +# ifndef alloca /* predefined by HP cc +Olibcalls */ +char *alloca(size_t size); +# endif +# else +# ifdef alloca +# undef alloca +# endif +# define alloca(sz) malloc(sz) /* Kludge this for now */ +# endif +# endif +# endif +#elif !defined(alloca) +#define alloca __builtin_alloca +#endif + +#ifndef HAVE_STRLCPY +size_t strlcpy(char *d, const char *s, size_t bufsize); +#endif + +#ifndef HAVE_STRLCAT +size_t strlcat(char *d, const char *s, size_t bufsize); +#endif + +#if HAVE_MCHECK_H && defined(__GNUC__) +static inline char * +xstrdup(const char *s) +{ + size_t memsize = strlen(s) + 1; + char *ptr = malloc(memsize); + if (!ptr) { + fprintf(stderr, "virtual memory exhausted.\n"); + exit(EXIT_FAILURE); + } + strlcpy(ptr, s, memsize); + return ptr; +} +#else +#define xstrdup(_str) strdup(_str) +#endif /* HAVE_MCHECK_H && defined(__GNUC__) */ + +#if HAVE___SECURE_GETENV && !defined(__LCLINT__) +#define getenv(_s) __secure_getenv(_s) +#endif + +#if !defined HAVE_SNPRINTF || !defined HAVE_C99_VSNPRINTF +#define snprintf rsync_snprintf +int snprintf(char *str,size_t count,const char *fmt,...); +#endif + +#define UNUSED(x) x __attribute__((__unused__)) + +#define PACKAGE "rsync" + +#include "popt.h" diff --git a/rsync/prepare-source b/rsync/prepare-source new file mode 100755 index 0000000..0e73138 --- /dev/null +++ b/rsync/prepare-source @@ -0,0 +1,51 @@ +#!/bin/sh +# Either use autoconf and autoheader to create configure.sh and config.h.in +# or (optionally) fetch the latest development versions of generated files. +# +# Specify one action or more than one to provide a fall-back: +# +# build build the config files [the default w/no arg] +# fetch fetch the latest dev config files +# fetchgen fetch all the latest dev generated files +# fetchSRC fetch the latest dev source files [NON-GENERATED FILES] +# +# The script stops after the first successful action. + +dir=`dirname $0` +if test x"$dir" != x -a x"$dir" != x.; then + cd "$dir" +fi + +if test $# = 0; then + set -- build +fi + +for action in "${@}"; do + case "$action" in + build|make) + make -f prepare-source.mak + ;; + fetch) + if perl --version >/dev/null 2>/dev/null; then + files='c*' + else + files='[cp]*' + fi + rsync -pvz rsync://rsync.samba.org/rsyncftp/generated-files/"$files" . + ;; + fetchgen) + rsync -pvz rsync://rsync.samba.org/rsyncftp/generated-files/'*' . + ;; + fetchSRC) + rsync -pvrz --exclude=/.git/ rsync://rsync.samba.org/ftp/pub/unpacked/rsync/ . + ;; + *) + echo "Unknown action: $action" + exit 1 + esac + if test $? = 0; then + exit + fi +done + +exit 1 diff --git a/rsync/prepare-source.mak b/rsync/prepare-source.mak new file mode 100644 index 0000000..054bab7 --- /dev/null +++ b/rsync/prepare-source.mak @@ -0,0 +1,7 @@ +conf: configure.sh config.h.in + +configure.sh: configure.ac aclocal.m4 + autoconf -o configure.sh + +config.h.in: configure.ac aclocal.m4 + autoheader && touch config.h.in diff --git a/rsync/progress.c b/rsync/progress.c new file mode 100644 index 0000000..318a77f --- /dev/null +++ b/rsync/progress.c @@ -0,0 +1,221 @@ +/* + * Routines to output progress information during a file transfer. + * + * Copyright (C) 1996-2000 Andrew Tridgell + * Copyright (C) 1996 Paul Mackerras + * Copyright (C) 2001, 2002 Martin Pool + * Copyright (C) 2003-2014 Wayne Davison + * + * 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, visit the http://fsf.org website. + */ + +#include "rsync.h" +#include "inums.h" + +extern int am_server; +extern int flist_eof; +extern int need_unsorted_flist; +extern int output_needs_newline; +extern struct stats stats; +extern struct file_list *cur_flist; + +#define PROGRESS_HISTORY_SECS 5 + +#ifdef GETPGRP_VOID +#define GETPGRP_ARG +#else +#define GETPGRP_ARG 0 +#endif + +struct progress_history { + struct timeval time; + OFF_T ofs; +}; + +static struct progress_history ph_start; +static struct progress_history ph_list[PROGRESS_HISTORY_SECS]; +static int newest_hpos, oldest_hpos; +static int current_file_index; + +static unsigned long msdiff(struct timeval *t1, struct timeval *t2) +{ + return (t2->tv_sec - t1->tv_sec) * 1000L + + (t2->tv_usec - t1->tv_usec) / 1000; +} + + +/** + * @param ofs Current position in file + * @param size Total size of file + * @param is_last True if this is the last time progress will be + * printed for this file, so we should output a newline. (Not + * necessarily the same as all bytes being received.) + **/ +static void rprint_progress(OFF_T ofs, OFF_T size, struct timeval *now, + int is_last) +{ + char rembuf[64], eol[128]; + const char *units; + unsigned long diff; + double rate, remain; + int pct; + + if (is_last) { + int len = snprintf(eol, sizeof eol, + " (xfr#%d, %s-chk=%d/%d)\n", + stats.xferred_files, flist_eof ? "to" : "ir", + stats.num_files - current_file_index - 1, + stats.num_files); + if (INFO_GTE(PROGRESS, 2)) { + static int last_len = 0; + /* Drop \n and pad with spaces if line got shorter. */ + if (last_len < --len) + last_len = len; + eol[last_len] = '\0'; + while (last_len > len) + eol[--last_len] = ' '; + is_last = 0; + } + /* Compute stats based on the starting info. */ + if (!ph_start.time.tv_sec + || !(diff = msdiff(&ph_start.time, now))) + diff = 1; + rate = (double) (ofs - ph_start.ofs) * 1000.0 / diff / 1024.0; + /* Switch to total time taken for our last update. */ + remain = (double) diff / 1000.0; + } else { + strlcpy(eol, " ", sizeof eol); + /* Compute stats based on recent progress. */ + if (!(diff = msdiff(&ph_list[oldest_hpos].time, now))) + diff = 1; + rate = (double) (ofs - ph_list[oldest_hpos].ofs) * 1000.0 + / diff / 1024.0; + remain = rate ? (double) (size - ofs) / rate / 1000.0 : 0.0; + } + + if (rate > 1024*1024) { + rate /= 1024.0 * 1024.0; + units = "GB/s"; + } else if (rate > 1024) { + rate /= 1024.0; + units = "MB/s"; + } else { + units = "kB/s"; + } + + if (remain < 0) + strlcpy(rembuf, " ??:??:??", sizeof rembuf); + else { + snprintf(rembuf, sizeof rembuf, "%4d:%02d:%02d", + (int) (remain / 3600.0), + (int) (remain / 60.0) % 60, + (int) remain % 60); + } + + output_needs_newline = 0; + pct = ofs == size ? 100 : (int) (100.0 * ofs / size); + rprintf(FCLIENT, "\r%15s %3d%% %7.2f%s %s%s", + human_num(ofs), pct, rate, units, rembuf, eol); + if (!is_last) { + output_needs_newline = 1; + rflush(FCLIENT); + } +} + +void set_current_file_index(struct file_struct *file, int ndx) +{ + if (!file) + current_file_index = cur_flist->used + cur_flist->ndx_start - 1; + else if (need_unsorted_flist) + current_file_index = flist_find(cur_flist, file) + cur_flist->ndx_start; + else + current_file_index = ndx; + current_file_index -= cur_flist->flist_num; +} + +void end_progress(OFF_T size) +{ + if (!am_server) { + struct timeval now; + gettimeofday(&now, NULL); + if (INFO_GTE(PROGRESS, 2)) { + rprint_progress(stats.total_transferred_size, + stats.total_size, &now, True); + } else { + rprint_progress(size, size, &now, True); + memset(&ph_start, 0, sizeof ph_start); + } + } +} + +void show_progress(OFF_T ofs, OFF_T size) +{ + struct timeval now; +#if defined HAVE_GETPGRP && defined HAVE_TCGETPGRP + static pid_t pgrp = -1; + pid_t tc_pgrp; +#endif + + if (am_server) + return; + +#if defined HAVE_GETPGRP && defined HAVE_TCGETPGRP + if (pgrp == -1) + pgrp = getpgrp(GETPGRP_ARG); +#endif + + gettimeofday(&now, NULL); + + if (INFO_GTE(PROGRESS, 2)) { + ofs = stats.total_transferred_size - size + ofs; + size = stats.total_size; + } + + if (!ph_start.time.tv_sec) { + int i; + + /* Try to guess the real starting time when the sender started + * to send us data by using the time we last received some data + * in the last file (if it was recent enough). */ + if (msdiff(&ph_list[newest_hpos].time, &now) <= 1500) { + ph_start.time = ph_list[newest_hpos].time; + ph_start.ofs = 0; + } else { + ph_start.time.tv_sec = now.tv_sec; + ph_start.time.tv_usec = now.tv_usec; + ph_start.ofs = ofs; + } + + for (i = 0; i < PROGRESS_HISTORY_SECS; i++) + ph_list[i] = ph_start; + } + else { + if (msdiff(&ph_list[newest_hpos].time, &now) < 1000) + return; + + newest_hpos = oldest_hpos; + oldest_hpos = (oldest_hpos + 1) % PROGRESS_HISTORY_SECS; + ph_list[newest_hpos].time.tv_sec = now.tv_sec; + ph_list[newest_hpos].time.tv_usec = now.tv_usec; + ph_list[newest_hpos].ofs = ofs; + } + +#if defined HAVE_GETPGRP && defined HAVE_TCGETPGRP + tc_pgrp = tcgetpgrp(STDOUT_FILENO); + if (tc_pgrp != pgrp && tc_pgrp != -1) + return; +#endif + + rprint_progress(ofs, size, &now, False); +} diff --git a/rsync/proto.h b/rsync/proto.h new file mode 100644 index 0000000..22fc539 --- /dev/null +++ b/rsync/proto.h @@ -0,0 +1,423 @@ +/* This file is automatically generated with "make proto". DO NOT EDIT */ + +int allow_access(const char *addr, const char **host_ptr, int i); +void free_acl(stat_x *sxp); +int get_acl(const char *fname, stat_x *sxp); +void send_acl(int f, stat_x *sxp); +void receive_acl(int f, struct file_struct *file); +void cache_tmp_acl(struct file_struct *file, stat_x *sxp); +void uncache_tmp_acls(void); +int set_acl(const char *fname, const struct file_struct *file, stat_x *sxp, mode_t new_mode); +void match_acl_ids(void); +int default_perms_for_dir(const char *dir); +void base64_encode(const char *buf, int len, char *out, int pad); +char *auth_server(int f_in, int f_out, int module, const char *host, + const char *addr, const char *leader); +void auth_client(int fd, const char *user, const char *challenge); +char *get_backup_name(const char *fname); +int make_backup(const char *fname, BOOL prefer_rename); +void write_stream_flags(int fd); +void read_stream_flags(int fd); +void check_batch_flags(void); +void write_batch_shell_file(int argc, char *argv[], int file_arg_cnt); +uint32 get_checksum1(char *buf1, int32 len); +void get_checksum2(char *buf, int32 len, char *sum); +void file_checksum(const char *fname, const STRUCT_STAT *st_p, char *sum); +void sum_init(int seed); +void sum_update(const char *p, int32 len); +int sum_end(char *sum); +struct chmod_mode_struct *parse_chmod(const char *modestr, + struct chmod_mode_struct **root_mode_ptr); +int tweak_mode(int mode, struct chmod_mode_struct *chmod_modes); +int free_chmod_mode(struct chmod_mode_struct *chmod_modes); +void close_all(void); +NORETURN void _exit_cleanup(int code, const char *file, int line); +void cleanup_disable(void); +void cleanup_set(const char *fnametmp, const char *fname, struct file_struct *file, + int fd_r, int fd_w); +void cleanup_set_pid(pid_t pid); +char *client_addr(int fd); +char *client_name(int fd); +void client_sockaddr(int fd, + struct sockaddr_storage *ss, + socklen_t *ss_len); +int lookup_name(int fd, const struct sockaddr_storage *ss, + socklen_t ss_len, + char *name_buf, size_t name_buf_size, + char *port_buf, size_t port_buf_size); +int compare_addrinfo_sockaddr(const struct addrinfo *ai, + const struct sockaddr_storage *ss); +int check_name(int fd, + const struct sockaddr_storage *ss, + char *name_buf, size_t name_buf_size); +int start_socket_client(char *host, int remote_argc, char *remote_argv[], + int argc, char *argv[]); +int start_inband_exchange(int f_in, int f_out, const char *user, int argc, char *argv[]); +int start_daemon(int f_in, int f_out); +int daemon_main(void); +void set_allow_inc_recurse(void); +void setup_protocol(int f_out,int f_in); +int claim_connection(char *fname, int max_connections); +enum delret delete_item(char *fbuf, uint16 mode, uint16 flags); +uint16 get_del_for_flag(uint16 mode); +void set_filter_dir(const char *dir, unsigned int dirlen); +void *push_local_filters(const char *dir, unsigned int dirlen); +void pop_local_filters(void *mem); +void change_local_filter_dir(const char *dname, int dlen, int dir_depth); +int check_filter(filter_rule_list *listp, enum logcode code, + const char *name, int name_is_dir); +const filter_rule *rule_template(uint32 rflags); +void parse_filter_str(filter_rule_list *listp, const char *rulestr, + const filter_rule *template, int xflags); +void parse_filter_file(filter_rule_list *listp, const char *fname, const filter_rule *template, int xflags); +char *get_rule_prefix(filter_rule *rule, const char *pat, int for_xfer, + unsigned int *plen_ptr); +void send_filter_list(int f_out); +void recv_filter_list(int f_in); +int sparse_end(int f, OFF_T size); +int flush_write_file(int f); +int write_file(int f, char *buf, int len); +struct map_struct *map_file(int fd, OFF_T len, int32 read_size, int32 blk_size); +char *map_ptr(struct map_struct *map, OFF_T offset, int32 len); +int unmap_file(struct map_struct *map); +void init_flist(void); +void show_flist_stats(void); +int link_stat(const char *path, STRUCT_STAT *stp, int follow_dirlinks); +int change_pathname(struct file_struct *file, const char *dir, int dirlen); +struct file_struct *make_file(const char *fname, struct file_list *flist, + STRUCT_STAT *stp, int flags, int filter_level); +void unmake_file(struct file_struct *file); +void send_extra_file_list(int f, int at_least); +struct file_list *send_file_list(int f, int argc, char *argv[]); +struct file_list *recv_file_list(int f); +void recv_additional_file_list(int f); +int flist_find(struct file_list *flist, struct file_struct *f); +int flist_find_ignore_dirness(struct file_list *flist, struct file_struct *f); +void clear_file(struct file_struct *file); +struct file_list *flist_new(int flags, char *msg); +void flist_free(struct file_list *flist); +int f_name_cmp(const struct file_struct *f1, const struct file_struct *f2); +int f_name_has_prefix(const struct file_struct *f1, const struct file_struct *f2); +char *f_name_buf(void); +char *f_name(const struct file_struct *f, char *fbuf); +struct file_list *get_dirlist(char *dirname, int dlen, int flags); +int unchanged_attrs(const char *fname, struct file_struct *file, stat_x *sxp); +void itemize(const char *fnamecmp, struct file_struct *file, int ndx, int statret, + stat_x *sxp, int32 iflags, uchar fnamecmp_type, + const char *xname); +int unchanged_file(char *fn, struct file_struct *file, STRUCT_STAT *st); +int atomic_create(struct file_struct *file, char *fname, const char *slnk, const char *hlnk, + dev_t rdev, stat_x *sxp, int del_for_flag); +void check_for_finished_files(int itemizing, enum logcode code, int check_redo); +void generate_files(int f_out, const char *local_name); +struct hashtable *hashtable_create(int size, int key64); +void hashtable_destroy(struct hashtable *tbl); +void *hashtable_find(struct hashtable *tbl, int64 key, int allocate_if_missing); +void init_hard_links(void); +struct ht_int64_node *idev_find(int64 dev, int64 ino); +void idev_destroy(void); +void match_hard_links(struct file_list *flist); +int hard_link_check(struct file_struct *file, int ndx, char *fname, + int statret, stat_x *sxp, int itemizing, + enum logcode code); +int hard_link_one(struct file_struct *file, const char *fname, + const char *oldname, int terse); +void finish_hard_link(struct file_struct *file, const char *fname, int fin_ndx, + STRUCT_STAT *stp, int itemizing, enum logcode code, + int alt_dest); +int skip_hard_link(struct file_struct *file, struct file_list **flist_p); +void reduce_iobuf_size(xbuf *out, size_t new_size); +void restore_iobuf_size(xbuf *out); +void noop_io_until_death(void); +int send_msg(enum msgcode code, const char *buf, size_t len, int convert); +void send_msg_int(enum msgcode code, int num); +void io_set_sock_fds(int f_in, int f_out); +void set_io_timeout(int secs); +void increment_active_files(int ndx, int itemizing, enum logcode code); +int get_redo_num(void); +int get_hlink_num(void); +void start_filesfrom_forwarding(int fd); +int read_line(int fd, char *buf, size_t bufsiz, int flags); +void read_args(int f_in, char *mod_name, char *buf, size_t bufsiz, int rl_nulls, + char ***argv_p, int *argc_p, char **request_p); +BOOL io_start_buffering_out(int f_out); +BOOL io_start_buffering_in(int f_in); +void io_end_buffering_in(BOOL free_buffers); +void io_end_buffering_out(BOOL free_buffers); +void maybe_flush_socket(int important); +void maybe_send_keepalive(time_t now, int flags); +void start_flist_forward(int ndx); +void stop_flist_forward(void); +void wait_for_receiver(void); +unsigned short read_shortint(int f); +int32 read_int(int f); +int32 read_varint(int f); +int64 read_varlong(int f, uchar min_bytes); +int64 read_longint(int f); +void read_buf(int f, char *buf, size_t len); +void read_sbuf(int f, char *buf, size_t len); +uchar read_byte(int f); +int read_vstring(int f, char *buf, int bufsize); +void read_sum_head(int f, struct sum_struct *sum); +void write_sum_head(int f, struct sum_struct *sum); +void io_flush(int flush_it_all); +void write_shortint(int f, unsigned short x); +void write_int(int f, int32 x); +void write_varint(int f, int32 x); +void write_varlong(int f, int64 x, uchar min_bytes); +void write_longint(int f, int64 x); +void write_bigbuf(int f, const char *buf, size_t len); +void write_buf(int f, const char *buf, size_t len); +void write_sbuf(int f, const char *buf); +void write_byte(int f, uchar c); +void write_vstring(int f, const char *str, int len); +void write_ndx(int f, int32 ndx); +int32 read_ndx(int f); +int read_line_old(int fd, char *buf, size_t bufsiz, int eof_ok); +void io_printf(int fd, const char *format, ...); +void io_start_multiplex_out(int fd); +void io_start_multiplex_in(int fd); +int io_end_multiplex_in(int mode); +int io_end_multiplex_out(int mode); +void start_write_batch(int fd); +void stop_write_batch(void); +char *lp_bind_address(void); +char *lp_motd_file(void); +char *lp_pid_file(void); +char *lp_socket_options(void); +int lp_listen_backlog(void); +int lp_rsync_port(void); +char *lp_auth_users(int module_id); +char *lp_charset(int module_id); +char *lp_comment(int module_id); +char *lp_dont_compress(int module_id); +char *lp_exclude(int module_id); +char *lp_exclude_from(int module_id); +char *lp_filter(int module_id); +char *lp_gid(int module_id); +char *lp_hosts_allow(int module_id); +char *lp_hosts_deny(int module_id); +char *lp_include(int module_id); +char *lp_include_from(int module_id); +char *lp_incoming_chmod(int module_id); +char *lp_lock_file(int module_id); +char *lp_log_file(int module_id); +char *lp_log_format(int module_id); +char *lp_name(int module_id); +char *lp_outgoing_chmod(int module_id); +char *lp_path(int module_id); +char *lp_postxfer_exec(int module_id); +char *lp_prexfer_exec(int module_id); +char *lp_refuse_options(int module_id); +char *lp_secrets_file(int module_id); +char *lp_temp_dir(int module_id); +char *lp_uid(int module_id); +int lp_max_connections(int module_id); +int lp_max_verbosity(int module_id); +int lp_syslog_facility(int module_id); +int lp_timeout(int module_id); +BOOL lp_fake_super(int module_id); +BOOL lp_forward_lookup(int module_id); +BOOL lp_ignore_errors(int module_id); +BOOL lp_ignore_nonreadable(int module_id); +BOOL lp_list(int module_id); +BOOL lp_munge_symlinks(int module_id); +BOOL lp_numeric_ids(int module_id); +BOOL lp_read_only(int module_id); +BOOL lp_reverse_lookup(int module_id); +BOOL lp_strict_modes(int module_id); +BOOL lp_transfer_logging(int module_id); +BOOL lp_use_chroot(int module_id); +BOOL lp_write_only(int module_id); +int lp_load(char *pszFname, int globals_only); +BOOL set_dparams(int syntax_check_only); +int lp_num_modules(void); +int lp_number(char *name); +void log_init(int restart); +void logfile_close(void); +void logfile_reopen(void); +void rwrite(enum logcode code, const char *buf, int len, int is_utf8); +void rprintf(enum logcode code, const char *format, ...); +void rsyserr(enum logcode code, int errcode, const char *format, ...); +void rflush(enum logcode code); +void remember_initial_stats(void); +int log_format_has(const char *format, char esc); +void log_item(enum logcode code, struct file_struct *file, int iflags, const char *hlink); +void maybe_log_item(struct file_struct *file, int iflags, int itemizing, + const char *buf); +void log_delete(const char *fname, int mode); +void log_exit(int code, const char *file, int line); +pid_t wait_process(pid_t pid, int *status_ptr, int flags); +void write_del_stats(int f); +void read_del_stats(int f); +int child_main(int argc, char *argv[]); +void start_server(int f_in, int f_out, int argc, char *argv[]); +int client_run(int f_in, int f_out, pid_t pid, int argc, char *argv[]); +RETSIGTYPE remember_children(UNUSED(int val)); +const char *get_panic_action(void); +int main(int argc,char *argv[]); +void match_sums(int f, struct sum_struct *s, struct map_struct *buf, OFF_T len); +void match_report(void); +void limit_output_verbosity(int level); +void reset_output_levels(void); +void negate_output_levels(void); +void usage(enum logcode F); +void option_error(void); +int parse_arguments(int *argc_p, const char ***argv_p); +void server_options(char **args, int *argc_p); +char *check_for_hostspec(char *s, char **host_ptr, int *port_ptr); +int pm_process( char *FileName, + BOOL (*sfunc)(char *), + BOOL (*pfunc)(char *, char *) ); +pid_t piped_child(char **command, int *f_in, int *f_out); +pid_t local_child(int argc, char **argv, int *f_in, int *f_out, + int (*child_main)(int, char*[])); +void set_current_file_index(struct file_struct *file, int ndx); +void end_progress(OFF_T size); +void show_progress(OFF_T ofs, OFF_T size); +int get_tmpname(char *fnametmp, const char *fname, BOOL make_unique); +int open_tmpfile(char *fnametmp, const char *fname, struct file_struct *file); +int recv_files(int f_in, int f_out, char *local_name); +void setup_iconv(void); +int iconvbufs(iconv_t ic, xbuf *in, xbuf *out, int flags); +void send_protected_args(int fd, char *args[]); +int read_ndx_and_attrs(int f_in, int f_out, int *iflag_ptr, uchar *type_ptr, + char *buf, int *len_ptr); +void free_sums(struct sum_struct *s); +mode_t dest_mode(mode_t flist_mode, mode_t stat_mode, int dflt_perms, + int exists); +int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp, + const char *fnamecmp, int flags); +RETSIGTYPE sig_int(int sig_num); +int finish_transfer(const char *fname, const char *fnametmp, + const char *fnamecmp, const char *partialptr, + struct file_struct *file, int ok_to_set_time, + int overwriting_basis); +struct file_list *flist_for_ndx(int ndx, const char *fatal_error_loc); +const char *who_am_i(void); +void successful_send(int ndx); +void send_files(int f_in, int f_out); +int try_bind_local(int s, int ai_family, int ai_socktype, + const char *bind_addr); +int open_socket_out(char *host, int port, const char *bind_addr, + int af_hint); +int open_socket_out_wrapped(char *host, int port, const char *bind_addr, + int af_hint); +int is_a_socket(int fd); +void start_accept_loop(int port, int (*fn)(int, int)); +void set_socket_options(int fd, char *options); +int do_unlink(const char *fname); +int do_symlink(const char *lnk, const char *fname); +ssize_t do_readlink(const char *path, char *buf, size_t bufsiz); +int do_link(const char *fname1, const char *fname2); +int do_lchown(const char *path, uid_t owner, gid_t group); +int do_mknod(const char *pathname, mode_t mode, dev_t dev); +int do_rmdir(const char *pathname); +int do_open(const char *pathname, int flags, mode_t mode); +int do_chmod(const char *path, mode_t mode); +int do_rename(const char *fname1, const char *fname2); +int do_ftruncate(int fd, OFF_T size); +void trim_trailing_slashes(char *name); +int do_mkdir(char *fname, mode_t mode); +int do_mkstemp(char *template, mode_t perms); +int do_stat(const char *fname, STRUCT_STAT *st); +int do_lstat(const char *fname, STRUCT_STAT *st); +int do_fstat(int fd, STRUCT_STAT *st); +OFF_T do_lseek(int fd, OFF_T offset, int whence); +int do_utimensat(const char *fname, time_t modtime, uint32 mod_nsec); +int do_lutimes(const char *fname, time_t modtime, uint32 mod_nsec); +int do_utimes(const char *fname, time_t modtime, uint32 mod_nsec); +int do_utime(const char *fname, time_t modtime, UNUSED(uint32 mod_nsec)); +int do_fallocate(int fd, OFF_T offset, OFF_T length); +int do_open_nofollow(const char *pathname, int flags); +void set_compression(const char *fname); +void send_token(int f, int32 token, struct map_struct *buf, OFF_T offset, + int32 n, int32 toklen); +int32 recv_token(int f, char **data); +void see_token(char *data, int32 toklen); +char *uid_to_user(uid_t uid); +char *gid_to_group(gid_t gid); +int user_to_uid(const char *name, uid_t *uid_p, BOOL num_ok); +int group_to_gid(const char *name, gid_t *gid_p, BOOL num_ok); +uid_t match_uid(uid_t uid); +gid_t match_gid(gid_t gid, uint16 *flags_ptr); +const char *add_uid(uid_t uid); +const char *add_gid(gid_t gid); +void send_id_list(int f); +uid_t recv_user_name(int f, uid_t uid); +gid_t recv_group_name(int f, gid_t gid, uint16 *flags_ptr); +void recv_id_list(int f, struct file_list *flist); +void parse_name_map(char *map, BOOL usernames); +const char *getallgroups(uid_t uid, gid_t *gid_list, int *size_ptr); +void set_nonblocking(int fd); +void set_blocking(int fd); +int fd_pair(int fd[2]); +void print_child_argv(const char *prefix, char **cmd); +int set_modtime(const char *fname, time_t modtime, uint32 mod_nsec, mode_t mode); +int make_path(char *fname, int flags); +int full_write(int desc, const char *ptr, size_t len); +int copy_file(const char *source, const char *dest, int ofd, mode_t mode); +int robust_unlink(const char *fname); +int robust_rename(const char *from, const char *to, const char *partialptr, + int mode); +pid_t do_fork(void); +void kill_all(int sig); +int lock_range(int fd, int offset, int len); +int glob_expand(const char *arg, char ***argv_p, int *argc_p, int *maxargs_p); +void glob_expand_module(char *base1, char *arg, char ***argv_p, int *argc_p, int *maxargs_p); +void strlower(char *s); +size_t pathjoin(char *dest, size_t destsize, const char *p1, const char *p2); +size_t stringjoin(char *dest, size_t destsize, ...); +int count_dir_elements(const char *p); +int clean_fname(char *name, int flags); +char *sanitize_path(char *dest, const char *p, const char *rootdir, int depth, + int flags); +int change_dir(const char *dir, int set_path_only); +char *normalize_path(char *path, BOOL force_newbuf, unsigned int *len_ptr); +char *full_fname(const char *fn); +char *partial_dir_fname(const char *fname); +int handle_partial_dir(const char *fname, int create); +int unsafe_symlink(const char *dest, const char *src); +char *timestring(time_t t); +int cmp_time(time_t file1, time_t file2); +int _Insure_trap_error(int a1, int a2, int a3, int a4, int a5, int a6); +const char *find_filename_suffix(const char *fn, int fn_len, int *len_ptr); +uint32 fuzzy_distance(const char *s1, unsigned len1, const char *s2, unsigned len2); +struct bitbag *bitbag_create(int max_ndx); +void bitbag_set_bit(struct bitbag *bb, int ndx); +void bitbag_clear_bit(struct bitbag *bb, int ndx); +int bitbag_check_bit(struct bitbag *bb, int ndx); +int bitbag_next_bit(struct bitbag *bb, int after); +void flist_ndx_push(flist_ndx_list *lp, int ndx); +int flist_ndx_pop(flist_ndx_list *lp); +void *expand_item_list(item_list *lp, size_t item_size, + const char *desc, int incr); +int msleep(int t); +void *_new_array(unsigned long num, unsigned int size, int use_calloc); +void *_realloc_array(void *ptr, unsigned int size, size_t num); +const char *sum_as_hex(const char *sum); +NORETURN void out_of_memory(const char *str); +NORETURN void overflow_exit(const char *str); +void free_xattr(stat_x *sxp); +int get_xattr(const char *fname, stat_x *sxp); +int copy_xattrs(const char *source, const char *dest); +int send_xattr(int f, stat_x *sxp); +int xattr_diff(struct file_struct *file, stat_x *sxp, int find_all); +void send_xattr_request(const char *fname, struct file_struct *file, int f_out); +int recv_xattr_request(struct file_struct *file, int f_in); +void receive_xattr(int f, struct file_struct *file); +void cache_tmp_xattr(struct file_struct *file, stat_x *sxp); +void uncache_tmp_xattrs(void); +int set_xattr(const char *fname, const struct file_struct *file, + const char *fnamecmp, stat_x *sxp); +char *get_xattr_acl(const char *fname, int is_access_acl, size_t *len_p); +int set_xattr_acl(const char *fname, int is_access_acl, const char *buf, size_t buf_len); +int del_def_xattr_acl(const char *fname); +int get_stat_xattr(const char *fname, int fd, STRUCT_STAT *fst, STRUCT_STAT *xst); +int set_stat_xattr(const char *fname, struct file_struct *file, mode_t new_mode); +int x_stat(const char *fname, STRUCT_STAT *fst, STRUCT_STAT *xst); +int x_lstat(const char *fname, STRUCT_STAT *fst, STRUCT_STAT *xst); +int x_fstat(int fd, STRUCT_STAT *fst, STRUCT_STAT *xst); +int sys_gettimeofday(struct timeval *tv); +char *do_big_num(int64 num, int human_flag, const char *fract); +char *do_big_dnum(double dnum, int human_flag, int decimal_digits); diff --git a/rsync/proto.h-tstamp b/rsync/proto.h-tstamp new file mode 100644 index 0000000..e69de29 diff --git a/rsync/receiver.c b/rsync/receiver.c new file mode 100644 index 0000000..571b7da --- /dev/null +++ b/rsync/receiver.c @@ -0,0 +1,953 @@ +/* + * Routines only used by the receiving process. + * + * Copyright (C) 1996-2000 Andrew Tridgell + * Copyright (C) 1996 Paul Mackerras + * Copyright (C) 2003-2014 Wayne Davison + * + * 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, visit the http://fsf.org website. + */ + +#include "rsync.h" +#include "inums.h" + +extern int dry_run; +extern int do_xfers; +extern int am_root; +extern int am_server; +extern int inc_recurse; +extern int log_before_transfer; +extern int stdout_format_has_i; +extern int logfile_format_has_i; +extern int want_xattr_optim; +extern int csum_length; +extern int read_batch; +extern int write_batch; +extern int batch_gen_fd; +extern int protocol_version; +extern int relative_paths; +extern int preserve_hard_links; +extern int preserve_perms; +extern int preserve_xattrs; +extern int basis_dir_cnt; +extern int make_backups; +extern int cleanup_got_literal; +extern int remove_source_files; +extern int append_mode; +extern int sparse_files; +extern int preallocate_files; +extern int keep_partial; +extern int checksum_len; +extern int checksum_seed; +extern int inplace; +extern int allowed_lull; +extern int delay_updates; +extern mode_t orig_umask; +extern struct stats stats; +extern char *tmpdir; +extern char *partial_dir; +extern char *basis_dir[MAX_BASIS_DIRS+1]; +extern char sender_file_sum[MAX_DIGEST_LEN]; +extern struct file_list *cur_flist, *first_flist, *dir_flist; +extern filter_rule_list daemon_filter_list; + +static struct bitbag *delayed_bits = NULL; +static int phase = 0, redoing = 0; +static flist_ndx_list batch_redo_list; +/* We're either updating the basis file or an identical copy: */ +static int updating_basis_or_equiv; + +#define TMPNAME_SUFFIX ".XXXXXX" +#define TMPNAME_SUFFIX_LEN ((int)sizeof TMPNAME_SUFFIX - 1) +#define MAX_UNIQUE_NUMBER 999999 +#define MAX_UNIQUE_LOOP 100 + +/* get_tmpname() - create a tmp filename for a given filename + * + * If a tmpdir is defined, use that as the directory to put it in. Otherwise, + * the tmp filename is in the same directory as the given name. Note that + * there may be no directory at all in the given name! + * + * The tmp filename is basically the given filename with a dot prepended, and + * .XXXXXX appended (for mkstemp() to put its unique gunk in). We take care + * to not exceed either the MAXPATHLEN or NAME_MAX, especially the last, as + * the basename basically becomes 8 characters longer. In such a case, the + * original name is shortened sufficiently to make it all fit. + * + * If the make_unique arg is True, the XXXXXX string is replaced with a unique + * string that doesn't exist at the time of the check. This is intended to be + * used for creating hard links, symlinks, devices, and special files, since + * normal files should be handled by mkstemp() for safety. + * + * Of course, the only reason the file is based on the original name is to + * make it easier to figure out what purpose a temp file is serving when a + * transfer is in progress. */ +int get_tmpname(char *fnametmp, const char *fname, BOOL make_unique) +{ + int maxname, length = 0; + const char *f; + char *suf; + + if (tmpdir) { + /* Note: this can't overflow, so the return value is safe */ + length = strlcpy(fnametmp, tmpdir, MAXPATHLEN - 2); + fnametmp[length++] = '/'; + } + + if ((f = strrchr(fname, '/')) != NULL) { + ++f; + if (!tmpdir) { + length = f - fname; + /* copy up to and including the slash */ + strlcpy(fnametmp, fname, length + 1); + } + } else + f = fname; + + if (!tmpdir) { /* using a tmpdir avoids the leading dot on our temp names */ + if (*f == '.') /* avoid an extra leading dot for OS X's sake */ + f++; + fnametmp[length++] = '.'; + } + + /* The maxname value is bufsize, and includes space for the '\0'. + * NAME_MAX needs an extra -1 for the name's leading dot. */ + maxname = MIN(MAXPATHLEN - length - TMPNAME_SUFFIX_LEN, + NAME_MAX - 1 - TMPNAME_SUFFIX_LEN); + + if (maxname < 0) { + rprintf(FERROR_XFER, "temporary filename too long: %s\n", fname); + fnametmp[0] = '\0'; + return 0; + } + + if (maxname) { + int added = strlcpy(fnametmp + length, f, maxname); + if (added >= maxname) + added = maxname - 1; + suf = fnametmp + length + added; + + /* Trim any dangling high-bit chars if the first-trimmed char (if any) is + * also a high-bit char, just in case we cut into a multi-byte sequence. + * We are guaranteed to stop because of the leading '.' we added. */ + if ((int)f[added] & 0x80) { + while ((int)suf[-1] & 0x80) + suf--; + } + /* trim one trailing dot before our suffix's dot */ + if (suf[-1] == '.') + suf--; + } else + suf = fnametmp + length - 1; /* overwrite the leading dot with suffix's dot */ + + if (make_unique) { + static unsigned counter_limit; + unsigned counter; + + if (!counter_limit) { + counter_limit = (unsigned)getpid() + MAX_UNIQUE_LOOP; + if (counter_limit > MAX_UNIQUE_NUMBER || counter_limit < MAX_UNIQUE_LOOP) + counter_limit = MAX_UNIQUE_LOOP; + } + counter = counter_limit - MAX_UNIQUE_LOOP; + + /* This doesn't have to be very good because we don't need + * to worry about someone trying to guess the values: all + * a conflict will do is cause a device, special file, hard + * link, or symlink to fail to be created. Also: avoid + * using mktemp() due to gcc's annoying warning. */ + while (1) { + snprintf(suf, TMPNAME_SUFFIX_LEN+1, ".%d", counter); + if (access(fnametmp, 0) < 0) + break; + if (++counter >= counter_limit) + return 0; + } + } else + memcpy(suf, TMPNAME_SUFFIX, TMPNAME_SUFFIX_LEN+1); + + return 1; +} + +/* Opens a temporary file for writing. + * Success: Writes name into fnametmp, returns fd. + * Failure: Clobbers fnametmp, returns -1. + * Calling cleanup_set() is the caller's job. */ +int open_tmpfile(char *fnametmp, const char *fname, struct file_struct *file) +{ + int fd; + mode_t added_perms; + + if (!get_tmpname(fnametmp, fname, False)) + return -1; + + if (am_root < 0) { + /* For --fake-super, the file must be useable by the copying + * user, just like it would be for root. */ + added_perms = S_IRUSR|S_IWUSR; + } else { + /* For a normal copy, we need to be able to tweak things like xattrs. */ + added_perms = S_IWUSR; + } + + /* We initially set the perms without the setuid/setgid bits or group + * access to ensure that there is no race condition. They will be + * correctly updated after the right owner and group info is set. + * (Thanks to snabb@epipe.fi for pointing this out.) */ + fd = do_mkstemp(fnametmp, (file->mode|added_perms) & INITACCESSPERMS); + +#if 0 + /* In most cases parent directories will already exist because their + * information should have been previously transferred, but that may + * not be the case with -R */ + if (fd == -1 && relative_paths && errno == ENOENT + && make_path(fnametmp, MKP_SKIP_SLASH | MKP_DROP_NAME) == 0) { + /* Get back to name with XXXXXX in it. */ + get_tmpname(fnametmp, fname, False); + fd = do_mkstemp(fnametmp, (file->mode|added_perms) & INITACCESSPERMS); + } +#endif + + if (fd == -1) { + rsyserr(FERROR_XFER, errno, "mkstemp %s failed", + full_fname(fnametmp)); + return -1; + } + + return fd; +} + +static int receive_data(int f_in, char *fname_r, int fd_r, OFF_T size_r, + const char *fname, int fd, OFF_T total_size) +{ + static char file_sum1[MAX_DIGEST_LEN]; + struct map_struct *mapbuf; + struct sum_struct sum; + int32 len; + OFF_T offset = 0; + OFF_T offset2; + char *data; + int32 i; + char *map = NULL; +#ifdef SUPPORT_PREALLOCATION +#ifdef PREALLOCATE_NEEDS_TRUNCATE + OFF_T preallocated_len = 0; +#endif + + if (preallocate_files && fd != -1 && total_size > 0 && (!inplace || total_size > size_r)) { + /* Try to preallocate enough space for file's eventual length. Can + * reduce fragmentation on filesystems like ext4, xfs, and NTFS. */ + if (do_fallocate(fd, 0, total_size) == 0) { +#ifdef PREALLOCATE_NEEDS_TRUNCATE + preallocated_len = total_size; +#endif + } else + rsyserr(FWARNING, errno, "do_fallocate %s", full_fname(fname)); + } +#endif + + read_sum_head(f_in, &sum); + + if (fd_r >= 0 && size_r > 0) { + int32 read_size = MAX(sum.blength * 2, 16*1024); + mapbuf = map_file(fd_r, size_r, read_size, sum.blength); + if (DEBUG_GTE(DELTASUM, 2)) { + rprintf(FINFO, "recv mapped %s of size %s\n", + fname_r, big_num(size_r)); + } + } else + mapbuf = NULL; + + sum_init(checksum_seed); + + if (append_mode > 0) { + OFF_T j; + sum.flength = (OFF_T)sum.count * sum.blength; + if (sum.remainder) + sum.flength -= sum.blength - sum.remainder; + if (append_mode == 2 && mapbuf) { + for (j = CHUNK_SIZE; j < sum.flength; j += CHUNK_SIZE) { + if (INFO_GTE(PROGRESS, 1)) + show_progress(offset, total_size); + sum_update(map_ptr(mapbuf, offset, CHUNK_SIZE), + CHUNK_SIZE); + offset = j; + } + if (offset < sum.flength) { + int32 len = (int32)(sum.flength - offset); + if (INFO_GTE(PROGRESS, 1)) + show_progress(offset, total_size); + sum_update(map_ptr(mapbuf, offset, len), len); + } + } + offset = sum.flength; + if (fd != -1 && (j = do_lseek(fd, offset, SEEK_SET)) != offset) { + rsyserr(FERROR_XFER, errno, "lseek of %s returned %s, not %s", + full_fname(fname), big_num(j), big_num(offset)); + exit_cleanup(RERR_FILEIO); + } + } + + while ((i = recv_token(f_in, &data)) != 0) { + if (INFO_GTE(PROGRESS, 1)) + show_progress(offset, total_size); + + if (allowed_lull) + maybe_send_keepalive(time(NULL), MSK_ALLOW_FLUSH | MSK_ACTIVE_RECEIVER); + + if (i > 0) { + if (DEBUG_GTE(DELTASUM, 3)) { + rprintf(FINFO,"data recv %d at %s\n", + i, big_num(offset)); + } + + stats.literal_data += i; + cleanup_got_literal = 1; + + sum_update(data, i); + + if (fd != -1 && write_file(fd,data,i) != i) + goto report_write_error; + offset += i; + continue; + } + + i = -(i+1); + offset2 = i * (OFF_T)sum.blength; + len = sum.blength; + if (i == (int)sum.count-1 && sum.remainder != 0) + len = sum.remainder; + + stats.matched_data += len; + + if (DEBUG_GTE(DELTASUM, 3)) { + rprintf(FINFO, + "chunk[%d] of size %ld at %s offset=%s%s\n", + i, (long)len, big_num(offset2), big_num(offset), + updating_basis_or_equiv && offset == offset2 ? " (seek)" : ""); + } + + if (mapbuf) { + map = map_ptr(mapbuf,offset2,len); + + see_token(map, len); + sum_update(map, len); + } + + if (updating_basis_or_equiv) { + if (offset == offset2 && fd != -1) { + OFF_T pos; + if (flush_write_file(fd) < 0) + goto report_write_error; + offset += len; + if ((pos = do_lseek(fd, len, SEEK_CUR)) != offset) { + rsyserr(FERROR_XFER, errno, + "lseek of %s returned %s, not %s", + full_fname(fname), + big_num(pos), big_num(offset)); + exit_cleanup(RERR_FILEIO); + } + continue; + } + } + if (fd != -1 && map && write_file(fd, map, len) != (int)len) + goto report_write_error; + offset += len; + } + + if (flush_write_file(fd) < 0) + goto report_write_error; + +#ifdef HAVE_FTRUNCATE + /* inplace: New data could be shorter than old data. + * preallocate_files: total_size could have been an overestimate. + * Cut off any extra preallocated zeros from dest file. */ + if ((inplace +#ifdef PREALLOCATE_NEEDS_TRUNCATE + || preallocated_len > offset +#endif + ) && fd != -1 && do_ftruncate(fd, offset) < 0) { + rsyserr(FERROR_XFER, errno, "ftruncate failed on %s", + full_fname(fname)); + } +#endif + + if (INFO_GTE(PROGRESS, 1)) + end_progress(total_size); + + if (fd != -1 && offset > 0 && sparse_end(fd, offset) != 0) { + report_write_error: + rsyserr(FERROR_XFER, errno, "write failed on %s", + full_fname(fname)); + exit_cleanup(RERR_FILEIO); + } + + if (sum_end(file_sum1) != checksum_len) + overflow_exit("checksum_len"); /* Impossible... */ + + if (mapbuf) + unmap_file(mapbuf); + + read_buf(f_in, sender_file_sum, checksum_len); + if (DEBUG_GTE(DELTASUM, 2)) + rprintf(FINFO,"got file_sum\n"); + if (fd != -1 && memcmp(file_sum1, sender_file_sum, checksum_len) != 0) + return 0; + return 1; +} + + +static void discard_receive_data(int f_in, OFF_T length) +{ + receive_data(f_in, NULL, -1, 0, NULL, -1, length); +} + +static void handle_delayed_updates(char *local_name) +{ + char *fname, *partialptr; + int ndx; + + for (ndx = -1; (ndx = bitbag_next_bit(delayed_bits, ndx)) >= 0; ) { + struct file_struct *file = cur_flist->files[ndx]; + fname = local_name ? local_name : f_name(file, NULL); + if ((partialptr = partial_dir_fname(fname)) != NULL) { + if (make_backups > 0 && !make_backup(fname, False)) + continue; + if (DEBUG_GTE(RECV, 1)) { + rprintf(FINFO, "renaming %s to %s\n", + partialptr, fname); + } + /* We don't use robust_rename() here because the + * partial-dir must be on the same drive. */ + if (do_rename(partialptr, fname) < 0) { + rsyserr(FERROR_XFER, errno, + "rename failed for %s (from %s)", + full_fname(fname), partialptr); + } else { + if (remove_source_files + || (preserve_hard_links && F_IS_HLINKED(file))) + send_msg_int(MSG_SUCCESS, ndx); + handle_partial_dir(partialptr, PDIR_DELETE); + } + } + } +} + +static void no_batched_update(int ndx, BOOL is_redo) +{ + struct file_list *flist = flist_for_ndx(ndx, "no_batched_update"); + struct file_struct *file = flist->files[ndx - flist->ndx_start]; + + rprintf(FERROR_XFER, "(No batched update for%s \"%s\")\n", + is_redo ? " resend of" : "", f_name(file, NULL)); + + if (inc_recurse && !dry_run) + send_msg_int(MSG_NO_SEND, ndx); +} + +static int we_want_redo(int desired_ndx) +{ + static int redo_ndx = -1; + + while (redo_ndx < desired_ndx) { + if (redo_ndx >= 0) + no_batched_update(redo_ndx, True); + if ((redo_ndx = flist_ndx_pop(&batch_redo_list)) < 0) + return 0; + } + + if (redo_ndx == desired_ndx) { + redo_ndx = -1; + return 1; + } + + return 0; +} + +static int gen_wants_ndx(int desired_ndx, int flist_num) +{ + static int next_ndx = -1; + static int done_cnt = 0; + static BOOL got_eof = False; + + if (got_eof) + return 0; + + /* TODO: integrate gen-reading I/O into perform_io() so this is not needed? */ + io_flush(FULL_FLUSH); + + while (next_ndx < desired_ndx) { + if (inc_recurse && flist_num <= done_cnt) + return 0; + if (next_ndx >= 0) + no_batched_update(next_ndx, False); + if ((next_ndx = read_int(batch_gen_fd)) < 0) { + if (inc_recurse) { + done_cnt++; + continue; + } + got_eof = True; + return 0; + } + } + + if (next_ndx == desired_ndx) { + next_ndx = -1; + return 1; + } + + return 0; +} + +/** + * main routine for receiver process. + * + * Receiver process runs on the same host as the generator process. */ +int recv_files(int f_in, int f_out, char *local_name) +{ + int fd1,fd2; + STRUCT_STAT st; + int iflags, xlen; + char *fname, fbuf[MAXPATHLEN]; + char xname[MAXPATHLEN]; + char fnametmp[MAXPATHLEN]; + char *fnamecmp, *partialptr; + char fnamecmpbuf[MAXPATHLEN]; + uchar fnamecmp_type; + struct file_struct *file; + int itemizing = am_server ? logfile_format_has_i : stdout_format_has_i; + enum logcode log_code = log_before_transfer ? FLOG : FINFO; + int max_phase = protocol_version >= 29 ? 2 : 1; + int dflt_perms = (ACCESSPERMS & ~orig_umask); +#ifdef SUPPORT_ACLS + const char *parent_dirname = ""; +#endif + int ndx, recv_ok; + + if (DEBUG_GTE(RECV, 1)) + rprintf(FINFO, "recv_files(%d) starting\n", cur_flist->used); + + if (delay_updates) + delayed_bits = bitbag_create(cur_flist->used + 1); + + while (1) { + cleanup_disable(); + + /* This call also sets cur_flist. */ + ndx = read_ndx_and_attrs(f_in, f_out, &iflags, &fnamecmp_type, + xname, &xlen); + if (ndx == NDX_DONE) { + if (!am_server && INFO_GTE(PROGRESS, 2) && cur_flist) { + set_current_file_index(NULL, 0); + end_progress(0); + } + if (inc_recurse && first_flist) { + if (read_batch) { + ndx = first_flist->used + first_flist->ndx_start; + gen_wants_ndx(ndx, first_flist->flist_num); + } + flist_free(first_flist); + if (first_flist) + continue; + } else if (read_batch && first_flist) { + ndx = first_flist->used; + gen_wants_ndx(ndx, first_flist->flist_num); + } + if (++phase > max_phase) + break; + if (DEBUG_GTE(RECV, 1)) + rprintf(FINFO, "recv_files phase=%d\n", phase); + if (phase == 2 && delay_updates) + handle_delayed_updates(local_name); + write_int(f_out, NDX_DONE); + continue; + } + + if (ndx - cur_flist->ndx_start >= 0) + file = cur_flist->files[ndx - cur_flist->ndx_start]; + else + file = dir_flist->files[cur_flist->parent_ndx]; + fname = local_name ? local_name : f_name(file, fbuf); + + if (DEBUG_GTE(RECV, 1)) + rprintf(FINFO, "recv_files(%s)\n", fname); + +#ifdef SUPPORT_XATTRS + if (preserve_xattrs && iflags & ITEM_REPORT_XATTR && do_xfers + && !(want_xattr_optim && BITS_SET(iflags, ITEM_XNAME_FOLLOWS|ITEM_LOCAL_CHANGE))) + recv_xattr_request(file, f_in); +#endif + + if (!(iflags & ITEM_TRANSFER)) { + maybe_log_item(file, iflags, itemizing, xname); +#ifdef SUPPORT_XATTRS + if (preserve_xattrs && iflags & ITEM_REPORT_XATTR && do_xfers + && !BITS_SET(iflags, ITEM_XNAME_FOLLOWS|ITEM_LOCAL_CHANGE)) + set_file_attrs(fname, file, NULL, fname, 0); +#endif + if (iflags & ITEM_IS_NEW) { + stats.created_files++; + if (S_ISREG(file->mode)) { + /* Nothing further to count. */ + } else if (S_ISDIR(file->mode)) + stats.created_dirs++; +#ifdef SUPPORT_LINKS + else if (S_ISLNK(file->mode)) + stats.created_symlinks++; +#endif + else if (IS_DEVICE(file->mode)) + stats.created_devices++; + else + stats.created_specials++; + } + continue; + } + if (phase == 2) { + rprintf(FERROR, + "got transfer request in phase 2 [%s]\n", + who_am_i()); + exit_cleanup(RERR_PROTOCOL); + } + + if (file->flags & FLAG_FILE_SENT) { + if (csum_length == SHORT_SUM_LENGTH) { + if (keep_partial && !partial_dir) + make_backups = -make_backups; /* prevents double backup */ + if (append_mode) + sparse_files = -sparse_files; + append_mode = -append_mode; + csum_length = SUM_LENGTH; + redoing = 1; + } + } else { + if (csum_length != SHORT_SUM_LENGTH) { + if (keep_partial && !partial_dir) + make_backups = -make_backups; + if (append_mode) + sparse_files = -sparse_files; + append_mode = -append_mode; + csum_length = SHORT_SUM_LENGTH; + redoing = 0; + } + if (iflags & ITEM_IS_NEW) + stats.created_files++; + } + + if (!am_server && INFO_GTE(PROGRESS, 1)) + set_current_file_index(file, ndx); + stats.xferred_files++; + stats.total_transferred_size += F_LENGTH(file); + + cleanup_got_literal = 0; + + if (daemon_filter_list.head + && check_filter(&daemon_filter_list, FLOG, fname, 0) < 0) { + rprintf(FERROR, "attempt to hack rsync failed.\n"); + exit_cleanup(RERR_PROTOCOL); + } + + if (read_batch) { + int wanted = redoing + ? we_want_redo(ndx) + : gen_wants_ndx(ndx, cur_flist->flist_num); + if (!wanted) { + rprintf(FINFO, + "(Skipping batched update for%s \"%s\")\n", + redoing ? " resend of" : "", + fname); + discard_receive_data(f_in, F_LENGTH(file)); + file->flags |= FLAG_FILE_SENT; + continue; + } + } + + if (!log_before_transfer) + remember_initial_stats(); + + if (!do_xfers) { /* log the transfer */ + log_item(FCLIENT, file, iflags, NULL); + if (read_batch) + discard_receive_data(f_in, F_LENGTH(file)); + continue; + } + if (write_batch < 0) { + log_item(FCLIENT, file, iflags, NULL); + if (!am_server) + discard_receive_data(f_in, F_LENGTH(file)); + if (inc_recurse) + send_msg_int(MSG_SUCCESS, ndx); + continue; + } + + partialptr = partial_dir ? partial_dir_fname(fname) : fname; + + if (protocol_version >= 29) { + switch (fnamecmp_type) { + case FNAMECMP_FNAME: + fnamecmp = fname; + break; + case FNAMECMP_PARTIAL_DIR: + fnamecmp = partialptr; + break; + case FNAMECMP_BACKUP: + fnamecmp = get_backup_name(fname); + break; + case FNAMECMP_FUZZY: + if (file->dirname) { + pathjoin(fnamecmpbuf, sizeof fnamecmpbuf, file->dirname, xname); + fnamecmp = fnamecmpbuf; + } else + fnamecmp = xname; + break; + default: + if (fnamecmp_type > FNAMECMP_FUZZY && fnamecmp_type-FNAMECMP_FUZZY <= basis_dir_cnt) { + fnamecmp_type -= FNAMECMP_FUZZY + 1; + if (file->dirname) { + stringjoin(fnamecmpbuf, sizeof fnamecmpbuf, + basis_dir[fnamecmp_type], "/", file->dirname, "/", xname, NULL); + } else + pathjoin(fnamecmpbuf, sizeof fnamecmpbuf, basis_dir[fnamecmp_type], xname); + } else if (fnamecmp_type >= basis_dir_cnt) { + rprintf(FERROR, + "invalid basis_dir index: %d.\n", + fnamecmp_type); + exit_cleanup(RERR_PROTOCOL); + } else + pathjoin(fnamecmpbuf, sizeof fnamecmpbuf, basis_dir[fnamecmp_type], fname); + fnamecmp = fnamecmpbuf; + break; + } + if (!fnamecmp || (daemon_filter_list.head + && check_filter(&daemon_filter_list, FLOG, fname, 0) < 0)) { + fnamecmp = fname; + fnamecmp_type = FNAMECMP_FNAME; + } + } else { + /* Reminder: --inplace && --partial-dir are never + * enabled at the same time. */ + if (inplace && make_backups > 0) { + if (!(fnamecmp = get_backup_name(fname))) + fnamecmp = fname; + else + fnamecmp_type = FNAMECMP_BACKUP; + } else if (partial_dir && partialptr) + fnamecmp = partialptr; + else + fnamecmp = fname; + } + + /* open the file */ + fd1 = do_open(fnamecmp, O_RDONLY, 0); + + if (fd1 == -1 && protocol_version < 29) { + if (fnamecmp != fname) { + fnamecmp = fname; + fd1 = do_open(fnamecmp, O_RDONLY, 0); + } + + if (fd1 == -1 && basis_dir[0]) { + /* pre-29 allowed only one alternate basis */ + pathjoin(fnamecmpbuf, sizeof fnamecmpbuf, + basis_dir[0], fname); + fnamecmp = fnamecmpbuf; + fd1 = do_open(fnamecmp, O_RDONLY, 0); + } + } + + updating_basis_or_equiv = inplace + && (fnamecmp == fname || fnamecmp_type == FNAMECMP_BACKUP); + + if (fd1 == -1) { + st.st_mode = 0; + st.st_size = 0; + } else if (do_fstat(fd1,&st) != 0) { + rsyserr(FERROR_XFER, errno, "fstat %s failed", + full_fname(fnamecmp)); + discard_receive_data(f_in, F_LENGTH(file)); + close(fd1); + if (inc_recurse) + send_msg_int(MSG_NO_SEND, ndx); + continue; + } + + if (fd1 != -1 && S_ISDIR(st.st_mode) && fnamecmp == fname) { + /* this special handling for directories + * wouldn't be necessary if robust_rename() + * and the underlying robust_unlink could cope + * with directories + */ + rprintf(FERROR_XFER, "recv_files: %s is a directory\n", + full_fname(fnamecmp)); + discard_receive_data(f_in, F_LENGTH(file)); + close(fd1); + if (inc_recurse) + send_msg_int(MSG_NO_SEND, ndx); + continue; + } + + if (fd1 != -1 && !S_ISREG(st.st_mode)) { + close(fd1); + fd1 = -1; + } + + /* If we're not preserving permissions, change the file-list's + * mode based on the local permissions and some heuristics. */ + if (!preserve_perms) { + int exists = fd1 != -1; +#ifdef SUPPORT_ACLS + const char *dn = file->dirname ? file->dirname : "."; + if (parent_dirname != dn + && strcmp(parent_dirname, dn) != 0) { + dflt_perms = default_perms_for_dir(dn); + parent_dirname = dn; + } +#endif + file->mode = dest_mode(file->mode, st.st_mode, + dflt_perms, exists); + } + + /* We now check to see if we are writing the file "inplace" */ + if (inplace) { + fd2 = do_open(fname, O_WRONLY|O_CREAT, 0600); + if (fd2 == -1) { + rsyserr(FERROR_XFER, errno, "open %s failed", + full_fname(fname)); + } else if (updating_basis_or_equiv) + cleanup_set(NULL, NULL, file, fd1, fd2); + } else { + fd2 = open_tmpfile(fnametmp, fname, file); + if (fd2 != -1) + cleanup_set(fnametmp, partialptr, file, fd1, fd2); + } + + if (fd2 == -1) { + discard_receive_data(f_in, F_LENGTH(file)); + if (fd1 != -1) + close(fd1); + if (inc_recurse) + send_msg_int(MSG_NO_SEND, ndx); + continue; + } + + /* log the transfer */ + if (log_before_transfer) + log_item(FCLIENT, file, iflags, NULL); + else if (!am_server && INFO_GTE(NAME, 1) && INFO_EQ(PROGRESS, 1)) + rprintf(FINFO, "%s\n", fname); + + /* recv file data */ + recv_ok = receive_data(f_in, fnamecmp, fd1, st.st_size, + fname, fd2, F_LENGTH(file)); + + log_item(log_code, file, iflags, NULL); + + if (fd1 != -1) + close(fd1); + if (close(fd2) < 0) { + rsyserr(FERROR, errno, "close failed on %s", + full_fname(fnametmp)); + exit_cleanup(RERR_FILEIO); + } + + if ((recv_ok && (!delay_updates || !partialptr)) || inplace) { + if (partialptr == fname) + partialptr = NULL; + if (!finish_transfer(fname, fnametmp, fnamecmp, + partialptr, file, recv_ok, 1)) + recv_ok = -1; + else if (fnamecmp == partialptr) { + do_unlink(partialptr); + handle_partial_dir(partialptr, PDIR_DELETE); + } + } else if (keep_partial && partialptr) { + if (!handle_partial_dir(partialptr, PDIR_CREATE)) { + rprintf(FERROR, + "Unable to create partial-dir for %s -- discarding %s.\n", + local_name ? local_name : f_name(file, NULL), + recv_ok ? "completed file" : "partial file"); + do_unlink(fnametmp); + recv_ok = -1; + } else if (!finish_transfer(partialptr, fnametmp, fnamecmp, NULL, + file, recv_ok, !partial_dir)) + recv_ok = -1; + else if (delay_updates && recv_ok) { + bitbag_set_bit(delayed_bits, ndx); + recv_ok = 2; + } else + partialptr = NULL; + } else + do_unlink(fnametmp); + + cleanup_disable(); + + if (read_batch) + file->flags |= FLAG_FILE_SENT; + + switch (recv_ok) { + case 2: + break; + case 1: + if (remove_source_files || inc_recurse + || (preserve_hard_links && F_IS_HLINKED(file))) + send_msg_int(MSG_SUCCESS, ndx); + break; + case 0: { + enum logcode msgtype = redoing ? FERROR_XFER : FWARNING; + if (msgtype == FERROR_XFER || INFO_GTE(NAME, 1)) { + char *errstr, *redostr, *keptstr; + if (!(keep_partial && partialptr) && !inplace) + keptstr = "discarded"; + else if (partial_dir) + keptstr = "put into partial-dir"; + else + keptstr = "retained"; + if (msgtype == FERROR_XFER) { + errstr = "ERROR"; + redostr = ""; + } else { + errstr = "WARNING"; + redostr = read_batch ? " (may try again)" + : " (will try again)"; + } + rprintf(msgtype, + "%s: %s failed verification -- update %s%s.\n", + errstr, local_name ? f_name(file, NULL) : fname, + keptstr, redostr); + } + if (!redoing) { + if (read_batch) + flist_ndx_push(&batch_redo_list, ndx); + send_msg_int(MSG_REDO, ndx); + file->flags |= FLAG_FILE_SENT; + } else if (inc_recurse) + send_msg_int(MSG_NO_SEND, ndx); + break; + } + case -1: + if (inc_recurse) + send_msg_int(MSG_NO_SEND, ndx); + break; + } + } + if (make_backups < 0) + make_backups = -make_backups; + + if (phase == 2 && delay_updates) /* for protocol_version < 29 */ + handle_delayed_updates(local_name); + + if (DEBUG_GTE(RECV, 1)) + rprintf(FINFO,"recv_files finished\n"); + + return 0; +} diff --git a/rsync/rounding.c b/rsync/rounding.c new file mode 100644 index 0000000..ea9604f --- /dev/null +++ b/rsync/rounding.c @@ -0,0 +1,38 @@ +/* + * A pre-compilation helper program to aid in the creation of rounding.h. + * + * Copyright (C) 2007-2014 Wayne Davison + * + * 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, visit the http://fsf.org website. + */ + +#include "rsync.h" + +#define ARRAY_LEN (EXTRA_ROUNDING+1) +#define SIZEOF(x) ((long int)sizeof (x)) + +struct test { + union file_extras extras[ARRAY_LEN]; + struct file_struct file; +}; + +#define ACTUAL_SIZE SIZEOF(struct test) +#define EXPECTED_SIZE (SIZEOF(union file_extras) * ARRAY_LEN + SIZEOF(struct file_struct)) + + int main(UNUSED(int argc), UNUSED(char *argv[])) +{ + static int test_array[1 - 2 * (ACTUAL_SIZE != EXPECTED_SIZE)]; + test_array[0] = 0; + return 0; +} diff --git a/rsync/rsync-3.1.1/.gitignore b/rsync/rsync-3.1.1/.gitignore new file mode 100644 index 0000000..948d3f7 --- /dev/null +++ b/rsync/rsync-3.1.1/.gitignore @@ -0,0 +1,46 @@ +*.[oa] +*~ +dummy +ID +Makefile +Makefile.old +configure.sh +configure.sh.old +config.cache +config.h +config.h.in +config.h.in.old +config.log +config.status +/proto.h +/proto.h-tstamp +/rsync.1 +/rsyncd.conf.5 +/autom4te*.cache +/confdefs.h +/conftest* +/dox +/getgroups +/gmon.out +/rsync +/rsync-ssl +/stunnel-rsync +/stunnel-rsyncd.conf +/shconfig +/testdir +/tests-dont-exist +/testtmp +/tls +/testrun +/trimslash +/t_unsafe +/wildtest +/getfsdev +/rounding.h +/doc/rsync.pdf +/doc/rsync.ps +/support/savetransfer +/testsuite/chown-fake.test +/testsuite/devices-fake.test +/testsuite/xattrs-hlink.test +/patches diff --git a/rsync/rsync-ssl.in b/rsync/rsync-ssl.in new file mode 100755 index 0000000..da58d6a --- /dev/null +++ b/rsync/rsync-ssl.in @@ -0,0 +1,12 @@ +#!/bin/bash +# This script supports using stunnel to secure an rsync daemon connection. +# Note that this requires at least version 4.x of stunnel. +case "$@" in +*rsync://*) ;; +*::*) ;; +*) + echo "You must use rsync-ssl with a daemon-style hostname." 0>&1 + exit 1 + ;; +esac +exec @bindir@/rsync --rsh=@bindir@/stunnel-rsync "${@}" diff --git a/rsync/rsync.c b/rsync/rsync.c new file mode 100644 index 0000000..c498c44 --- /dev/null +++ b/rsync/rsync.c @@ -0,0 +1,747 @@ +/* + * Routines common to more than one of the rsync processes. + * + * Copyright (C) 1996 Andrew Tridgell + * Copyright (C) 1996 Paul Mackerras + * Copyright (C) 2003-2014 Wayne Davison + * + * 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, visit the http://fsf.org website. + */ + +#include "rsync.h" +#include "ifuncs.h" +#if defined HAVE_LIBCHARSET_H && defined HAVE_LOCALE_CHARSET +#include +#elif defined HAVE_LANGINFO_H && defined HAVE_NL_LANGINFO +#include +#endif + +extern int dry_run; +extern int preserve_acls; +extern int preserve_xattrs; +extern int preserve_perms; +extern int preserve_executability; +extern int preserve_times; +extern int am_root; +extern int am_server; +extern int am_daemon; +extern int am_sender; +extern int am_receiver; +extern int am_generator; +extern int am_starting_up; +extern int allow_8bit_chars; +extern int protocol_version; +extern int got_kill_signal; +extern int inc_recurse; +extern int inplace; +extern int flist_eof; +extern int file_old_total; +extern int keep_dirlinks; +extern int make_backups; +extern struct file_list *cur_flist, *first_flist, *dir_flist; +extern struct chmod_mode_struct *daemon_chmod_modes; +#ifdef ICONV_OPTION +extern char *iconv_opt; +#endif + +#ifdef ICONV_CONST +iconv_t ic_chck = (iconv_t)-1; +# ifdef ICONV_OPTION +iconv_t ic_send = (iconv_t)-1, ic_recv = (iconv_t)-1; +# endif + +static const char *default_charset(void) +{ +# if defined HAVE_LIBCHARSET_H && defined HAVE_LOCALE_CHARSET + return locale_charset(); +# elif defined HAVE_LANGINFO_H && defined HAVE_NL_LANGINFO + return nl_langinfo(CODESET); +# else + return ""; /* Works with (at the very least) gnu iconv... */ +# endif +} + +void setup_iconv(void) +{ + const char *defset = default_charset(); +# ifdef ICONV_OPTION + const char *charset; + char *cp; +# endif + + if (!am_server && !allow_8bit_chars) { + /* It's OK if this fails... */ + ic_chck = iconv_open(defset, defset); + + if (DEBUG_GTE(ICONV, 2)) { + if (ic_chck == (iconv_t)-1) { + rprintf(FINFO, + "msg checking via isprint()" + " (iconv_open(\"%s\", \"%s\") errno: %d)\n", + defset, defset, errno); + } else { + rprintf(FINFO, + "msg checking charset: %s\n", + defset); + } + } + } else + ic_chck = (iconv_t)-1; + +# ifdef ICONV_OPTION + if (!iconv_opt) + return; + + if ((cp = strchr(iconv_opt, ',')) != NULL) { + if (am_server) /* A local transfer needs this. */ + iconv_opt = cp + 1; + else + *cp = '\0'; + } + + if (!*iconv_opt || (*iconv_opt == '.' && iconv_opt[1] == '\0')) + charset = defset; + else + charset = iconv_opt; + + if ((ic_send = iconv_open(UTF8_CHARSET, charset)) == (iconv_t)-1) { + rprintf(FERROR, "iconv_open(\"%s\", \"%s\") failed\n", + UTF8_CHARSET, charset); + exit_cleanup(RERR_UNSUPPORTED); + } + + if ((ic_recv = iconv_open(charset, UTF8_CHARSET)) == (iconv_t)-1) { + rprintf(FERROR, "iconv_open(\"%s\", \"%s\") failed\n", + charset, UTF8_CHARSET); + exit_cleanup(RERR_UNSUPPORTED); + } + + if (DEBUG_GTE(ICONV, 1)) { + rprintf(FINFO, "[%s] charset: %s\n", + who_am_i(), *charset ? charset : "[LOCALE]"); + } +# endif +} + +/* This function converts the chars in the "in" xbuf into characters in the + * "out" xbuf. The ".len" chars of the "in" xbuf is used starting from its + * ".pos". The ".size" of the "out" xbuf restricts how many characters can + * be stored, starting at its ".pos+.len" position. Note that the last byte + * of the "out" xbuf is not used, which reserves space for a trailing '\0' + * (though it is up to the caller to store a trailing '\0', as needed). + * + * We return a 0 on success or a -1 on error. An error also sets errno to + * E2BIG, EILSEQ, or EINVAL (see below); otherwise errno will be set to 0. + * The "in" xbuf is altered to update ".pos" and ".len". The "out" xbuf has + * data appended, and its ".len" incremented (see below for a ".size" note). + * + * If ICB_CIRCULAR_OUT is set in "flags", the chars going into the "out" xbuf + * can wrap around to the start, and the xbuf may have its ".size" reduced + * (presumably by 1 byte) if the iconv code doesn't have space to store a + * multi-byte character at the physical end of the ".buf" (though no reducing + * happens if ".pos" is <= 1, since there is no room to wrap around). + * + * If ICB_EXPAND_OUT is set in "flags", the "out" xbuf will be allocated if + * empty, and (as long as ICB_CIRCULAR_OUT is not set) expanded if too small. + * This prevents the return of E2BIG (except for a circular xbuf). + * + * If ICB_INCLUDE_BAD is set in "flags", any badly-encoded chars are included + * verbatim in the "out" xbuf, so EILSEQ will not be returned. + * + * If ICB_INCLUDE_INCOMPLETE is set in "flags", any incomplete multi-byte + * chars are included, which ensures that EINVAL is not returned. + * + * If ICB_INIT is set, the iconv() conversion state is initialized prior to + * processing the characters. */ +int iconvbufs(iconv_t ic, xbuf *in, xbuf *out, int flags) +{ + ICONV_CONST char *ibuf; + size_t icnt, ocnt, opos; + char *obuf; + + if (!out->size && flags & ICB_EXPAND_OUT) { + size_t siz = ROUND_UP_1024(in->len * 2); + alloc_xbuf(out, siz); + } else if (out->len+1 >= out->size) { + /* There is no room to even start storing data. */ + if (!(flags & ICB_EXPAND_OUT) || flags & ICB_CIRCULAR_OUT) { + errno = E2BIG; + return -1; + } + realloc_xbuf(out, out->size + ROUND_UP_1024(in->len * 2)); + } + + if (flags & ICB_INIT) + iconv(ic, NULL, 0, NULL, 0); + + ibuf = in->buf + in->pos; + icnt = in->len; + + opos = out->pos + out->len; + if (flags & ICB_CIRCULAR_OUT) { + if (opos >= out->size) { + opos -= out->size; + /* We know that out->pos is not 0 due to the "no room" check + * above, so this can't go "negative". */ + ocnt = out->pos - opos - 1; + } else { + /* Allow the use of all bytes to the physical end of the buffer + * unless pos is 0, in which case we reserve our trailing '\0'. */ + ocnt = out->size - opos - (out->pos ? 0 : 1); + } + } else + ocnt = out->size - opos - 1; + obuf = out->buf + opos; + + while (icnt) { + while (iconv(ic, &ibuf, &icnt, &obuf, &ocnt) == (size_t)-1) { + if (errno == EINTR) + continue; + if (errno == EINVAL) { + if (!(flags & ICB_INCLUDE_INCOMPLETE)) + goto finish; + if (!ocnt) + goto e2big; + } else if (errno == EILSEQ) { + if (!(flags & ICB_INCLUDE_BAD)) + goto finish; + if (!ocnt) + goto e2big; + } else if (errno == E2BIG) { + size_t siz; + e2big: + opos = obuf - out->buf; + if (flags & ICB_CIRCULAR_OUT && out->pos > 1 && opos > out->pos) { + /* We are in a divided circular buffer at the physical + * end with room to wrap to the start. If iconv() refused + * to use one or more trailing bytes in the buffer, we + * set the size to ignore the unused bytes. */ + if (opos < out->size) + reduce_iobuf_size(out, opos); + obuf = out->buf; + ocnt = out->pos - 1; + continue; + } + if (!(flags & ICB_EXPAND_OUT) || flags & ICB_CIRCULAR_OUT) { + errno = E2BIG; + goto finish; + } + siz = ROUND_UP_1024(in->len * 2); + realloc_xbuf(out, out->size + siz); + obuf = out->buf + opos; + ocnt += siz; + continue; + } else { + rsyserr(FERROR, errno, "unexpected error from iconv()"); + exit_cleanup(RERR_UNSUPPORTED); + } + *obuf++ = *ibuf++; + ocnt--, icnt--; + if (!icnt) + break; + } + } + + errno = 0; + + finish: + opos = obuf - out->buf; + if (flags & ICB_CIRCULAR_OUT && opos < out->pos) + opos += out->size; + out->len = opos - out->pos; + + in->len = icnt; + in->pos = ibuf - in->buf; + + return errno ? -1 : 0; +} +#endif + +void send_protected_args(int fd, char *args[]) +{ + int i; +#ifdef ICONV_OPTION + int convert = ic_send != (iconv_t)-1; + xbuf outbuf, inbuf; + + if (convert) + alloc_xbuf(&outbuf, 1024); +#endif + + for (i = 0; args[i]; i++) {} /* find first NULL */ + args[i] = "rsync"; /* set a new arg0 */ + if (DEBUG_GTE(CMD, 1)) + print_child_argv("protected args:", args + i + 1); + do { + if (!args[i][0]) + write_buf(fd, ".", 2); +#ifdef ICONV_OPTION + else if (convert) { + INIT_XBUF_STRLEN(inbuf, args[i]); + iconvbufs(ic_send, &inbuf, &outbuf, + ICB_EXPAND_OUT | ICB_INCLUDE_BAD | ICB_INCLUDE_INCOMPLETE | ICB_INIT); + outbuf.buf[outbuf.len] = '\0'; + write_buf(fd, outbuf.buf, outbuf.len + 1); + outbuf.len = 0; + } +#endif + else + write_buf(fd, args[i], strlen(args[i]) + 1); + } while (args[++i]); + write_byte(fd, 0); + +#ifdef ICONV_OPTION + if (convert) + free(outbuf.buf); +#endif +} + +int read_ndx_and_attrs(int f_in, int f_out, int *iflag_ptr, uchar *type_ptr, + char *buf, int *len_ptr) +{ + int len, iflags = 0; + struct file_list *flist; + uchar fnamecmp_type = FNAMECMP_FNAME; + int ndx; + + read_loop: + while (1) { + ndx = read_ndx(f_in); + + if (ndx >= 0) + break; + if (ndx == NDX_DONE) + return ndx; + if (ndx == NDX_DEL_STATS) { + read_del_stats(f_in); + if (am_sender && am_server) + write_del_stats(f_out); + continue; + } + if (!inc_recurse || am_sender) { + int last; + if (first_flist) + last = first_flist->prev->ndx_start + first_flist->prev->used - 1; + else + last = -1; + rprintf(FERROR, + "Invalid file index: %d (%d - %d) [%s]\n", + ndx, NDX_DONE, last, who_am_i()); + exit_cleanup(RERR_PROTOCOL); + } + if (ndx == NDX_FLIST_EOF) { + flist_eof = 1; + if (DEBUG_GTE(FLIST, 3)) + rprintf(FINFO, "[%s] flist_eof=1\n", who_am_i()); + write_int(f_out, NDX_FLIST_EOF); + continue; + } + ndx = NDX_FLIST_OFFSET - ndx; + if (ndx < 0 || ndx >= dir_flist->used) { + ndx = NDX_FLIST_OFFSET - ndx; + rprintf(FERROR, + "Invalid dir index: %d (%d - %d) [%s]\n", + ndx, NDX_FLIST_OFFSET, + NDX_FLIST_OFFSET - dir_flist->used + 1, + who_am_i()); + exit_cleanup(RERR_PROTOCOL); + } + + if (DEBUG_GTE(FLIST, 2)) { + rprintf(FINFO, "[%s] receiving flist for dir %d\n", + who_am_i(), ndx); + } + /* Send all the data we read for this flist to the generator. */ + start_flist_forward(ndx); + flist = recv_file_list(f_in); + flist->parent_ndx = ndx; + stop_flist_forward(); + } + + iflags = protocol_version >= 29 ? read_shortint(f_in) + : ITEM_TRANSFER | ITEM_MISSING_DATA; + + /* Support the protocol-29 keep-alive style. */ + if (protocol_version < 30 && ndx == cur_flist->used && iflags == ITEM_IS_NEW) { + if (am_sender) + maybe_send_keepalive(time(NULL), MSK_ALLOW_FLUSH); + goto read_loop; + } + + flist = flist_for_ndx(ndx, "read_ndx_and_attrs"); + if (flist != cur_flist) { + cur_flist = flist; + if (am_sender) { + file_old_total = cur_flist->used; + for (flist = first_flist; flist != cur_flist; flist = flist->next) + file_old_total += flist->used; + } + } + + if (iflags & ITEM_BASIS_TYPE_FOLLOWS) + fnamecmp_type = read_byte(f_in); + *type_ptr = fnamecmp_type; + + if (iflags & ITEM_XNAME_FOLLOWS) { + if ((len = read_vstring(f_in, buf, MAXPATHLEN)) < 0) + exit_cleanup(RERR_PROTOCOL); + } else { + *buf = '\0'; + len = -1; + } + *len_ptr = len; + + if (iflags & ITEM_TRANSFER) { + int i = ndx - cur_flist->ndx_start; + if (i < 0 || !S_ISREG(cur_flist->files[i]->mode)) { + rprintf(FERROR, + "received request to transfer non-regular file: %d [%s]\n", + ndx, who_am_i()); + exit_cleanup(RERR_PROTOCOL); + } + } + + *iflag_ptr = iflags; + return ndx; +} + +/* + free a sums struct + */ +void free_sums(struct sum_struct *s) +{ + if (s->sums) free(s->sums); + free(s); +} + +/* This is only called when we aren't preserving permissions. Figure out what + * the permissions should be and return them merged back into the mode. */ +mode_t dest_mode(mode_t flist_mode, mode_t stat_mode, int dflt_perms, + int exists) +{ + int new_mode; + /* If the file already exists, we'll return the local permissions, + * possibly tweaked by the --executability option. */ + if (exists) { + new_mode = (flist_mode & ~CHMOD_BITS) | (stat_mode & CHMOD_BITS); + if (preserve_executability && S_ISREG(flist_mode)) { + /* If the source file is executable, grant execute + * rights to everyone who can read, but ONLY if the + * file isn't already executable. */ + if (!(flist_mode & 0111)) + new_mode &= ~0111; + else if (!(stat_mode & 0111)) + new_mode |= (new_mode & 0444) >> 2; + } + } else { + /* Apply destination default permissions and turn + * off special permissions. */ + new_mode = flist_mode & (~CHMOD_BITS | dflt_perms); + } + return new_mode; +} + +int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp, + const char *fnamecmp, int flags) +{ + int updated = 0; + stat_x sx2; + int change_uid, change_gid; + mode_t new_mode = file->mode; + int inherit; + + if (!sxp) { + if (dry_run) + return 1; + if (link_stat(fname, &sx2.st, 0) < 0) { + rsyserr(FERROR_XFER, errno, "stat %s failed", + full_fname(fname)); + return 0; + } + init_stat_x(&sx2); + sxp = &sx2; + inherit = !preserve_perms; + } else + inherit = !preserve_perms && file->flags & FLAG_DIR_CREATED; + + if (inherit && S_ISDIR(new_mode) && sxp->st.st_mode & S_ISGID) { + /* We just created this directory and its setgid + * bit is on, so make sure it stays on. */ + new_mode |= S_ISGID; + } + + if (daemon_chmod_modes && !S_ISLNK(new_mode)) + new_mode = tweak_mode(new_mode, daemon_chmod_modes); + +#ifdef SUPPORT_ACLS + if (preserve_acls && !S_ISLNK(file->mode) && !ACL_READY(*sxp)) + get_acl(fname, sxp); +#endif + + change_uid = am_root && uid_ndx && sxp->st.st_uid != (uid_t)F_OWNER(file); + change_gid = gid_ndx && !(file->flags & FLAG_SKIP_GROUP) + && sxp->st.st_gid != (gid_t)F_GROUP(file); +#ifndef CAN_CHOWN_SYMLINK + if (S_ISLNK(sxp->st.st_mode)) { + ; + } else +#endif + if (change_uid || change_gid) { + if (DEBUG_GTE(OWN, 1)) { + if (change_uid) { + rprintf(FINFO, + "set uid of %s from %u to %u\n", + fname, (unsigned)sxp->st.st_uid, F_OWNER(file)); + } + if (change_gid) { + rprintf(FINFO, + "set gid of %s from %u to %u\n", + fname, (unsigned)sxp->st.st_gid, F_GROUP(file)); + } + } + if (am_root >= 0) { + uid_t uid = change_uid ? (uid_t)F_OWNER(file) : sxp->st.st_uid; + gid_t gid = change_gid ? (gid_t)F_GROUP(file) : sxp->st.st_gid; + if (do_lchown(fname, uid, gid) != 0) { + /* We shouldn't have attempted to change uid + * or gid unless have the privilege. */ + rsyserr(FERROR_XFER, errno, "%s %s failed", + change_uid ? "chown" : "chgrp", + full_fname(fname)); + goto cleanup; + } + if (uid == (uid_t)-1 && sxp->st.st_uid != (uid_t)-1) + rprintf(FERROR_XFER, "uid 4294967295 (-1) is impossible to set on %s\n", full_fname(fname)); + if (gid == (gid_t)-1 && sxp->st.st_gid != (gid_t)-1) + rprintf(FERROR_XFER, "gid 4294967295 (-1) is impossible to set on %s\n", full_fname(fname)); + /* A lchown had been done, so we need to re-stat if + * the destination had the setuid or setgid bits set + * (due to the side effect of the chown call). */ + if (sxp->st.st_mode & (S_ISUID | S_ISGID)) { + link_stat(fname, &sxp->st, + keep_dirlinks && S_ISDIR(sxp->st.st_mode)); + } + } + updated = 1; + } + +#ifdef SUPPORT_XATTRS + if (am_root < 0) + set_stat_xattr(fname, file, new_mode); + if (preserve_xattrs && fnamecmp) + set_xattr(fname, file, fnamecmp, sxp); +#endif + + if (!preserve_times + || (!(preserve_times & PRESERVE_DIR_TIMES) && S_ISDIR(sxp->st.st_mode)) + || (!(preserve_times & PRESERVE_LINK_TIMES) && S_ISLNK(sxp->st.st_mode))) + flags |= ATTRS_SKIP_MTIME; + if (!(flags & ATTRS_SKIP_MTIME) + && cmp_time(sxp->st.st_mtime, file->modtime) != 0) { + int ret = set_modtime(fname, file->modtime, F_MOD_NSEC(file), sxp->st.st_mode); + if (ret < 0) { + rsyserr(FERROR_XFER, errno, "failed to set times on %s", + full_fname(fname)); + goto cleanup; + } + if (ret == 0) /* ret == 1 if symlink could not be set */ + updated = 1; + else + file->flags |= FLAG_TIME_FAILED; + } + +#ifdef SUPPORT_ACLS + /* It's OK to call set_acl() now, even for a dir, as the generator + * will enable owner-writability using chmod, if necessary. + * + * If set_acl() changes permission bits in the process of setting + * an access ACL, it changes sxp->st.st_mode so we know whether we + * need to chmod(). */ + if (preserve_acls && !S_ISLNK(new_mode)) { + if (set_acl(fname, file, sxp, new_mode) > 0) + updated = 1; + } +#endif + +#ifdef HAVE_CHMOD + if (!BITS_EQUAL(sxp->st.st_mode, new_mode, CHMOD_BITS)) { + int ret = am_root < 0 ? 0 : do_chmod(fname, new_mode); + if (ret < 0) { + rsyserr(FERROR_XFER, errno, + "failed to set permissions on %s", + full_fname(fname)); + goto cleanup; + } + if (ret == 0) /* ret == 1 if symlink could not be set */ + updated = 1; + } +#endif + + if (INFO_GTE(NAME, 2) && flags & ATTRS_REPORT) { + if (updated) + rprintf(FCLIENT, "%s\n", fname); + else + rprintf(FCLIENT, "%s is uptodate\n", fname); + } + cleanup: + if (sxp == &sx2) + free_stat_x(&sx2); + return updated; +} + +/* This is only called for SIGINT, SIGHUP, and SIGTERM. */ +RETSIGTYPE sig_int(int sig_num) +{ + /* KLUGE: if the user hits Ctrl-C while ssh is prompting + * for a password, then our cleanup's sending of a SIGUSR1 + * signal to all our children may kill ssh before it has a + * chance to restore the tty settings (i.e. turn echo back + * on). By sleeping for a short time, ssh gets a bigger + * chance to do the right thing. If child processes are + * not ssh waiting for a password, then this tiny delay + * shouldn't hurt anything. */ + msleep(400); + + /* If we're an rsync daemon listener (not a daemon server), + * we'll exit with status 0 if we received SIGTERM. */ + if (am_daemon && !am_server && sig_num == SIGTERM) + exit_cleanup(0); + + /* If the signal arrived on the server side (or for the receiver + * process on the client), we want to try to do a controlled shutdown + * that lets the client side (generator process) know what happened. + * To do this, we set a flag and let the normal process handle the + * shutdown. We only attempt this if multiplexed IO is in effect and + * we didn't already set the flag. */ + if (!got_kill_signal && (am_server || am_receiver)) { + got_kill_signal = sig_num; + return; + } + + exit_cleanup(RERR_SIGNAL); +} + +/* Finish off a file transfer: renaming the file and setting the file's + * attributes (e.g. permissions, ownership, etc.). If the robust_rename() + * call is forced to copy the temp file and partialptr is both non-NULL and + * not an absolute path, we stage the file into the partial-dir and then + * rename it into place. This returns 1 on succcess or 0 on failure. */ +int finish_transfer(const char *fname, const char *fnametmp, + const char *fnamecmp, const char *partialptr, + struct file_struct *file, int ok_to_set_time, + int overwriting_basis) +{ + int ret; + const char *temp_copy_name = partialptr && *partialptr != '/' ? partialptr : NULL; + + if (inplace) { + if (DEBUG_GTE(RECV, 1)) + rprintf(FINFO, "finishing %s\n", fname); + fnametmp = fname; + goto do_set_file_attrs; + } + + if (make_backups > 0 && overwriting_basis) { + int ok = make_backup(fname, False); + if (!ok) + return 1; + if (ok == 1 && fnamecmp == fname) + fnamecmp = get_backup_name(fname); + } + + /* Change permissions before putting the file into place. */ + set_file_attrs(fnametmp, file, NULL, fnamecmp, + ok_to_set_time ? 0 : ATTRS_SKIP_MTIME); + + /* move tmp file over real file */ + if (DEBUG_GTE(RECV, 1)) + rprintf(FINFO, "renaming %s to %s\n", fnametmp, fname); + ret = robust_rename(fnametmp, fname, temp_copy_name, file->mode); + if (ret < 0) { + rsyserr(FERROR_XFER, errno, "%s %s -> \"%s\"", + ret == -2 ? "copy" : "rename", + full_fname(fnametmp), fname); + if (!partialptr || (ret == -2 && temp_copy_name) + || robust_rename(fnametmp, partialptr, NULL, file->mode) < 0) + do_unlink(fnametmp); + return 0; + } + if (ret == 0) { + /* The file was moved into place (not copied), so it's done. */ + return 1; + } + /* The file was copied, so tweak the perms of the copied file. If it + * was copied to partialptr, move it into its final destination. */ + fnametmp = temp_copy_name ? temp_copy_name : fname; + + do_set_file_attrs: + set_file_attrs(fnametmp, file, NULL, fnamecmp, + ok_to_set_time ? 0 : ATTRS_SKIP_MTIME); + + if (temp_copy_name) { + if (do_rename(fnametmp, fname) < 0) { + rsyserr(FERROR_XFER, errno, "rename %s -> \"%s\"", + full_fname(fnametmp), fname); + return 0; + } + handle_partial_dir(temp_copy_name, PDIR_DELETE); + } + return 1; +} + +struct file_list *flist_for_ndx(int ndx, const char *fatal_error_loc) +{ + struct file_list *flist = cur_flist; + + if (!flist && !(flist = first_flist)) + goto not_found; + + while (ndx < flist->ndx_start-1) { + if (flist == first_flist) + goto not_found; + flist = flist->prev; + } + while (ndx >= flist->ndx_start + flist->used) { + if (!(flist = flist->next)) + goto not_found; + } + return flist; + + not_found: + if (fatal_error_loc) { + int first, last; + if (first_flist) { + first = first_flist->ndx_start - 1; + last = first_flist->prev->ndx_start + first_flist->prev->used - 1; + } else { + first = 0; + last = -1; + } + rprintf(FERROR, + "File-list index %d not in %d - %d (%s) [%s]\n", + ndx, first, last, fatal_error_loc, who_am_i()); + exit_cleanup(RERR_PROTOCOL); + } + return NULL; +} + +const char *who_am_i(void) +{ + if (am_starting_up) + return am_server ? "server" : "client"; + return am_sender ? "sender" + : am_generator ? "generator" + : am_receiver ? "receiver" + : "Receiver"; /* pre-forked receiver */ +} diff --git a/rsync/rsync.h b/rsync/rsync.h new file mode 100644 index 0000000..4fef882 --- /dev/null +++ b/rsync/rsync.h @@ -0,0 +1,1294 @@ +/* + * Copyright (C) 1996, 2000 Andrew Tridgell + * Copyright (C) 1996 Paul Mackerras + * Copyright (C) 2001, 2002 Martin Pool + * Copyright (C) 2003-2014 Wayne Davison + * + * 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, visit the http://fsf.org website. + */ + +#define False 0 +#define True 1 + +#define BLOCK_SIZE 700 +#define RSYNC_RSH_ENV "RSYNC_RSH" +#define RSYNC_RSH_IO_ENV "RSYNC_RSH_IO" + +#define RSYNC_NAME "rsync" +/* RSYNCD_SYSCONF is now set in config.h */ +#define RSYNCD_USERCONF "rsyncd.conf" + +#define DEFAULT_LOCK_FILE "/var/run/rsyncd.lock" +#define URL_PREFIX "rsync://" + +#define SYMLINK_PREFIX "/rsyncd-munged/" /* This MUST have a trailing slash! */ +#define SYMLINK_PREFIX_LEN ((int)sizeof SYMLINK_PREFIX - 1) + +#define BACKUP_SUFFIX "~" + +/* a non-zero CHAR_OFFSET makes the rolling sum stronger, but is + incompatible with older versions :-( */ +#define CHAR_OFFSET 0 + +/* These flags are only used during the flist transfer. */ + +#define XMIT_TOP_DIR (1<<0) +#define XMIT_SAME_MODE (1<<1) +#define XMIT_SAME_RDEV_pre28 (1<<2) /* protocols 20 - 27 */ +#define XMIT_EXTENDED_FLAGS (1<<2) /* protocols 28 - now */ +#define XMIT_SAME_UID (1<<3) +#define XMIT_SAME_GID (1<<4) +#define XMIT_SAME_NAME (1<<5) +#define XMIT_LONG_NAME (1<<6) +#define XMIT_SAME_TIME (1<<7) +#define XMIT_SAME_RDEV_MAJOR (1<<8) /* protocols 28 - now (devices only) */ +#define XMIT_NO_CONTENT_DIR (1<<8) /* protocols 30 - now (dirs only) */ +#define XMIT_HLINKED (1<<9) /* protocols 28 - now */ +#define XMIT_SAME_DEV_pre30 (1<<10) /* protocols 28 - 29 */ +#define XMIT_USER_NAME_FOLLOWS (1<<10) /* protocols 30 - now */ +#define XMIT_RDEV_MINOR_8_pre30 (1<<11) /* protocols 28 - 29 */ +#define XMIT_GROUP_NAME_FOLLOWS (1<<11) /* protocols 30 - now */ +#define XMIT_HLINK_FIRST (1<<12) /* protocols 30 - now (HLINKED files only) */ +#define XMIT_IO_ERROR_ENDLIST (1<<12) /* protocols 31*- now (w/XMIT_EXTENDED_FLAGS) (also protocol 30 w/'f' compat flag) */ +#define XMIT_MOD_NSEC (1<<13) /* protocols 31 - now */ + +/* These flags are used in the live flist data. */ + +#define FLAG_TOP_DIR (1<<0) /* sender/receiver/generator */ +#define FLAG_OWNED_BY_US (1<<0) /* generator: set by make_file() for aux flists only */ +#define FLAG_FILE_SENT (1<<1) /* sender/receiver/generator */ +#define FLAG_DIR_CREATED (1<<1) /* generator */ +#define FLAG_CONTENT_DIR (1<<2) /* sender/receiver/generator */ +#define FLAG_MOUNT_DIR (1<<3) /* sender/generator (dirs only) */ +#define FLAG_SKIP_HLINK (1<<3) /* receiver/generator (w/FLAG_HLINKED) */ +#define FLAG_DUPLICATE (1<<4) /* sender */ +#define FLAG_MISSING_DIR (1<<4) /* generator */ +#define FLAG_HLINKED (1<<5) /* receiver/generator (checked on all types) */ +#define FLAG_HLINK_FIRST (1<<6) /* receiver/generator (w/FLAG_HLINKED) */ +#define FLAG_IMPLIED_DIR (1<<6) /* sender/receiver/generator (dirs only) */ +#define FLAG_HLINK_LAST (1<<7) /* receiver/generator */ +#define FLAG_HLINK_DONE (1<<8) /* receiver/generator (checked on all types) */ +#define FLAG_LENGTH64 (1<<9) /* sender/receiver/generator */ +#define FLAG_SKIP_GROUP (1<<10) /* receiver/generator */ +#define FLAG_TIME_FAILED (1<<11)/* generator */ +#define FLAG_MOD_NSEC (1<<12) /* sender/receiver/generator */ + +/* These flags are passed to functions but not stored. */ + +#define FLAG_DIVERT_DIRS (1<<16) /* sender, but must be unique */ + +/* These flags are for get_dirlist(). */ +#define GDL_IGNORE_FILTER_RULES (1<<0) + +/* Some helper macros for matching bits. */ +#define BITS_SET(val,bits) (((val) & (bits)) == (bits)) +#define BITS_SETnUNSET(val,onbits,offbits) (((val) & ((onbits)|(offbits))) == (onbits)) +#define BITS_EQUAL(b1,b2,mask) (((unsigned)(b1) & (unsigned)(mask)) \ + == ((unsigned)(b2) & (unsigned)(mask))) + +/* update this if you make incompatible changes */ +#define PROTOCOL_VERSION 31 + +/* This is used when working on a new protocol version in CVS, and should + * be a new non-zero value for each CVS change that affects the protocol. + * It must ALWAYS be 0 when the protocol goes final (and NEVER before)! */ +#define SUBPROTOCOL_VERSION 0 + +/* We refuse to interoperate with versions that are not in this range. + * Note that we assume we'll work with later versions: the onus is on + * people writing them to make sure that they don't send us anything + * we won't understand. + * + * Interoperation with old but supported protocol versions + * should cause a warning to be printed. At a future date + * the old protocol will become the minimum and + * compatibility code removed. + * + * There are two possible explanations for the limit at + * MAX_PROTOCOL_VERSION: either to allow new major-rev versions that + * do not interoperate with us, and (more likely) so that we can + * detect an attempt to connect rsync to a non-rsync server, which is + * unlikely to begin by sending a byte between MIN_PROTOCL_VERSION and + * MAX_PROTOCOL_VERSION. */ + +#define MIN_PROTOCOL_VERSION 20 +#define OLD_PROTOCOL_VERSION 25 +#define MAX_PROTOCOL_VERSION 40 + +#define MIN_FILECNT_LOOKAHEAD 1000 +#define MAX_FILECNT_LOOKAHEAD 10000 + +#define RSYNC_PORT 873 + +#define SPARSE_WRITE_SIZE (1024) +#define WRITE_SIZE (32*1024) +#define CHUNK_SIZE (32*1024) +#define MAX_MAP_SIZE (256*1024) +#define IO_BUFFER_SIZE (32*1024) +#define MAX_BLOCK_SIZE ((int32)1 << 17) + +/* For compatibility with older rsyncs */ +#define OLD_MAX_BLOCK_SIZE ((int32)1 << 29) + +#define ROUND_UP_1024(siz) ((siz) & (1024-1) ? ((siz) | (1024-1)) + 1 : (siz)) + +#define IOERR_GENERAL (1<<0) /* For backward compatibility, this must == 1 */ +#define IOERR_VANISHED (1<<1) +#define IOERR_DEL_LIMIT (1<<2) + +#define MAX_ARGS 1000 +#define MAX_BASIS_DIRS 20 +#define MAX_SERVER_ARGS (MAX_BASIS_DIRS*2 + 100) + +#define MPLEX_BASE 7 + +#define NO_FILTERS 0 +#define SERVER_FILTERS 1 +#define ALL_FILTERS 2 + +#define XFLG_FATAL_ERRORS (1<<0) +#define XFLG_OLD_PREFIXES (1<<1) +#define XFLG_ANCHORED2ABS (1<<2) /* leading slash indicates absolute */ +#define XFLG_ABS_IF_SLASH (1<<3) /* leading or interior slash is absolute */ +#define XFLG_DIR2WILD3 (1<<4) /* dir/ match gets trailing *** added */ + +#define ATTRS_REPORT (1<<0) +#define ATTRS_SKIP_MTIME (1<<1) + +#define FULL_FLUSH 1 +#define NORMAL_FLUSH 0 + +#define PDIR_CREATE 1 +#define PDIR_DELETE 0 + +/* Note: 0x00 - 0x7F are used for basis_dir[] indexes! */ +#define FNAMECMP_BASIS_DIR_LOW 0x00 /* Must remain 0! */ +#define FNAMECMP_BASIS_DIR_HIGH 0x7F +#define FNAMECMP_FNAME 0x80 +#define FNAMECMP_PARTIAL_DIR 0x81 +#define FNAMECMP_BACKUP 0x82 +#define FNAMECMP_FUZZY 0x83 + +/* For use by the itemize_changes code */ +#define ITEM_REPORT_ATIME (1<<0) +#define ITEM_REPORT_CHANGE (1<<1) +#define ITEM_REPORT_SIZE (1<<2) /* regular files only */ +#define ITEM_REPORT_TIMEFAIL (1<<2) /* symlinks only */ +#define ITEM_REPORT_TIME (1<<3) +#define ITEM_REPORT_PERMS (1<<4) +#define ITEM_REPORT_OWNER (1<<5) +#define ITEM_REPORT_GROUP (1<<6) +#define ITEM_REPORT_ACL (1<<7) +#define ITEM_REPORT_XATTR (1<<8) +#define ITEM_BASIS_TYPE_FOLLOWS (1<<11) +#define ITEM_XNAME_FOLLOWS (1<<12) +#define ITEM_IS_NEW (1<<13) +#define ITEM_LOCAL_CHANGE (1<<14) +#define ITEM_TRANSFER (1<<15) +/* These are outside the range of the transmitted flags. */ +#define ITEM_MISSING_DATA (1<<16) /* used by log_formatted() */ +#define ITEM_DELETED (1<<17) /* used by log_formatted() */ +#define ITEM_MATCHED (1<<18) /* used by itemize() */ + +#define SIGNIFICANT_ITEM_FLAGS (~(\ + ITEM_BASIS_TYPE_FOLLOWS | ITEM_XNAME_FOLLOWS | ITEM_LOCAL_CHANGE)) + +#define CFN_KEEP_DOT_DIRS (1<<0) +#define CFN_KEEP_TRAILING_SLASH (1<<1) +#define CFN_DROP_TRAILING_DOT_DIR (1<<2) +#define CFN_COLLAPSE_DOT_DOT_DIRS (1<<3) +#define CFN_REFUSE_DOT_DOT_DIRS (1<<4) + +#define SP_DEFAULT 0 +#define SP_KEEP_DOT_DIRS (1<<0) + +#define CD_NORMAL 0 +#define CD_SKIP_CHDIR 1 + +/* Log-message categories. FLOG only goes to the log file, not the client; + * FCLIENT is the opposite. */ +enum logcode { + FNONE=0, /* never sent */ + FERROR_XFER=1, FINFO=2, /* sent over socket for any protocol */ + FERROR=3, FWARNING=4, /* sent over socket for protocols >= 30 */ + FERROR_SOCKET=5, FLOG=6, /* only sent via receiver -> generator pipe */ + FERROR_UTF8=8, /* only sent via receiver -> generator pipe */ + FCLIENT=7 /* never transmitted (e.g. server converts to FINFO) */ +}; + +/* Messages types that are sent over the message channel. The logcode + * values must all be present here with identical numbers. */ +enum msgcode { + MSG_DATA=0, /* raw data on the multiplexed stream */ + MSG_ERROR_XFER=FERROR_XFER, MSG_INFO=FINFO, /* remote logging */ + MSG_ERROR=FERROR, MSG_WARNING=FWARNING, /* protocol-30 remote logging */ + MSG_ERROR_SOCKET=FERROR_SOCKET, /* sibling logging */ + MSG_ERROR_UTF8=FERROR_UTF8, /* sibling logging */ + MSG_LOG=FLOG, MSG_CLIENT=FCLIENT, /* sibling logging */ + MSG_REDO=9, /* reprocess indicated flist index */ + MSG_STATS=10, /* message has stats data for generator */ + MSG_IO_ERROR=22,/* the sending side had an I/O error */ + MSG_IO_TIMEOUT=33,/* tell client about a daemon's timeout value */ + MSG_NOOP=42, /* a do-nothing message (legacy protocol-30 only) */ + MSG_ERROR_EXIT=86, /* synchronize an error exit (siblings and protocol >= 31) */ + MSG_SUCCESS=100,/* successfully updated indicated flist index */ + MSG_DELETED=101,/* successfully deleted a file on receiving side */ + MSG_NO_SEND=102,/* sender failed to open a file we wanted */ +}; + +#define NDX_DONE -1 +#define NDX_FLIST_EOF -2 +#define NDX_DEL_STATS -3 +#define NDX_FLIST_OFFSET -101 + +/* For calling delete_item() and delete_dir_contents(). */ +#define DEL_NO_UID_WRITE (1<<0) /* file/dir has our uid w/o write perm */ +#define DEL_RECURSE (1<<1) /* if dir, delete all contents */ +#define DEL_DIR_IS_EMPTY (1<<2) /* internal delete_FUNCTIONS use only */ +#define DEL_FOR_FILE (1<<3) /* making room for a replacement file */ +#define DEL_FOR_DIR (1<<4) /* making room for a replacement dir */ +#define DEL_FOR_SYMLINK (1<<5) /* making room for a replacement symlink */ +#define DEL_FOR_DEVICE (1<<6) /* making room for a replacement device */ +#define DEL_FOR_SPECIAL (1<<7) /* making room for a replacement special */ +#define DEL_FOR_BACKUP (1<<8) /* the delete is for a backup operation */ + +#define DEL_MAKE_ROOM (DEL_FOR_FILE|DEL_FOR_DIR|DEL_FOR_SYMLINK|DEL_FOR_DEVICE|DEL_FOR_SPECIAL) + +enum delret { + DR_SUCCESS = 0, DR_FAILURE, DR_AT_LIMIT, DR_NOT_EMPTY +}; + +/* Defines for make_path() */ +#define MKP_DROP_NAME (1<<0) /* drop trailing filename or trailing slash */ +#define MKP_SKIP_SLASH (1<<1) /* skip one or more leading slashes */ + +/* Defines for maybe_send_keepalive() */ +#define MSK_ALLOW_FLUSH (1<<0) +#define MSK_ACTIVE_RECEIVER (1<<1) + +#include "errcode.h" + +#include "config.h" + +/* The default RSYNC_RSH is always set in config.h. */ + +#include +#ifdef HAVE_SYS_TYPES_H +# include +#endif +#ifdef HAVE_SYS_STAT_H +# include +#endif +#ifdef STDC_HEADERS +# include +# include +#else +# ifdef HAVE_STDLIB_H +# include +# endif +#endif +#ifdef HAVE_STRING_H +# if !defined STDC_HEADERS && defined HAVE_MEMORY_H +# include +# endif +# include +#endif +#ifdef HAVE_STRINGS_H +# include +#endif +#ifdef HAVE_INTTYPES_H +# include +#endif +#ifdef HAVE_STDINT_H +# include +#endif +#ifdef HAVE_UNISTD_H +# include +#endif + +#ifdef HAVE_SYS_PARAM_H +#include +#endif + +#if defined HAVE_MALLOC_H && (defined HAVE_MALLINFO || !defined HAVE_STDLIB_H) +#include +#endif + +#ifdef HAVE_SYS_SOCKET_H +#include +#endif + +#ifdef TIME_WITH_SYS_TIME +#include +#include +#else +#ifdef HAVE_SYS_TIME_H +#include +#else +#include +#endif +#endif + +#ifdef HAVE_FCNTL_H +#include +#else +#ifdef HAVE_SYS_FCNTL_H +#include +#endif +#endif + +#ifdef HAVE_SYS_IOCTL_H +#include +#endif + +#ifdef HAVE_SYS_FILIO_H +#include +#endif + +#include +#ifdef HAVE_SYS_WAIT_H +#include +#endif +#ifdef HAVE_CTYPE_H +#include +#endif +#ifdef HAVE_GRP_H +#include +#endif +#include + +#ifdef HAVE_UTIME_H +#include +#endif + +#if defined HAVE_UTIMENSAT || defined HAVE_LUTIMES +#define CAN_SET_SYMLINK_TIMES 1 +#endif + +#if defined HAVE_LCHOWN || defined CHOWN_MODIFIES_SYMLINK +#define CAN_CHOWN_SYMLINK 1 +#endif + +#if defined HAVE_LCHMOD || defined HAVE_SETATTRLIST +#define CAN_CHMOD_SYMLINK 1 +#endif + +#ifdef HAVE_UTIMENSAT +#ifdef HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC +#define ST_MTIME_NSEC st_mtim.tv_nsec +#elif defined(HAVE_STRUCT_STAT_ST_MTIMENSEC) +#define ST_MTIME_NSEC st_mtimensec +#endif +#endif + +#ifdef HAVE_SYS_SELECT_H +#include +#endif + +#ifdef HAVE_SYS_MODE_H +/* apparently AIX needs this for S_ISLNK */ +#ifndef S_ISLNK +#include +#endif +#endif + +/* these are needed for the uid/gid mapping code */ +#include +#include + +#include +#include +#include +#ifdef HAVE_NETDB_H +#include +#endif +#include +#include + +#ifdef HAVE_DIRENT_H +# include +#else +# define dirent direct +# ifdef HAVE_SYS_NDIR_H +# include +# endif +# ifdef HAVE_SYS_DIR_H +# include +# endif +# ifdef HAVE_NDIR_H +# include +# endif +#endif + +#ifdef MAJOR_IN_MKDEV +#include +# if !defined makedev && (defined mkdev || defined _WIN32 || defined __WIN32__) +# define makedev mkdev +# endif +#elif defined MAJOR_IN_SYSMACROS +#include +#endif + +#ifdef MAKEDEV_TAKES_3_ARGS +#define MAKEDEV(devmajor,devminor) makedev(0,devmajor,devminor) +#else +#define MAKEDEV(devmajor,devminor) makedev(devmajor,devminor) +#endif + +#ifdef HAVE_COMPAT_H +#include +#endif + +#ifdef HAVE_LIMITS_H +# include +#endif + +#if defined USE_ICONV_OPEN && defined HAVE_ICONV_H +#include +#ifndef ICONV_CONST +#define ICONV_CONST +#endif +#else +#ifdef ICONV_CONST +#undef ICONV_CONST +#endif +#ifdef ICONV_OPTION +#undef ICONV_OPTION +#endif +#ifdef iconv_t +#undef iconv_t +#endif +#define iconv_t int +#endif + +#include + +#include "lib/pool_alloc.h" + +#ifndef HAVE_ID_T +typedef unsigned int id_t; +#endif +#ifndef HAVE_PID_T +typedef int pid_t; +#endif +#ifndef HAVE_MODE_T +typedef unsigned int mode_t; +#endif +#ifndef HAVE_OFF_T +typedef long off_t; +#undef SIZEOF_OFF_T +#define SIZEOF_OFF_T SIZEOF_LONG +#endif +#ifndef HAVE_SIZE_T +typedef unsigned int size_t; +#endif + +#define BOOL int + +#ifndef uchar +#define uchar unsigned char +#endif + +#ifdef SIGNED_CHAR_OK +#define schar signed char +#else +#define schar char +#endif + +#ifndef int16 +#if SIZEOF_INT16_T == 2 +# define int16 int16_t +#else +# define int16 short +#endif +#endif + +#ifndef uint16 +#if SIZEOF_UINT16_T == 2 +# define uint16 uint16_t +#else +# define uint16 unsigned int16 +#endif +#endif + +/* Find a variable that is either exactly 32-bits or longer. + * If some code depends on 32-bit truncation, it will need to + * take special action in a "#if SIZEOF_INT32 > 4" section. */ +#ifndef int32 +#if SIZEOF_INT32_T == 4 +# define int32 int32_t +# define SIZEOF_INT32 4 +#elif SIZEOF_INT == 4 +# define int32 int +# define SIZEOF_INT32 4 +#elif SIZEOF_LONG == 4 +# define int32 long +# define SIZEOF_INT32 4 +#elif SIZEOF_SHORT == 4 +# define int32 short +# define SIZEOF_INT32 4 +#elif SIZEOF_INT > 4 +# define int32 int +# define SIZEOF_INT32 SIZEOF_INT +#elif SIZEOF_LONG > 4 +# define int32 long +# define SIZEOF_INT32 SIZEOF_LONG +#else +# error Could not find a 32-bit integer variable +#endif +#else +# define SIZEOF_INT32 4 +#endif + +#ifndef uint32 +#if SIZEOF_UINT32_T == 4 +# define uint32 uint32_t +#else +# define uint32 unsigned int32 +#endif +#endif + +#if SIZEOF_OFF_T == 8 || !SIZEOF_OFF64_T || !defined HAVE_STRUCT_STAT64 +#define OFF_T off_t +#define STRUCT_STAT struct stat +#define SIZEOF_CAPITAL_OFF_T SIZEOF_OFF_T +#else +#define OFF_T off64_t +#define STRUCT_STAT struct stat64 +#define USE_STAT64_FUNCS 1 +#define SIZEOF_CAPITAL_OFF_T SIZEOF_OFF64_T +#endif + +/* CAVEAT: on some systems, int64 will really be a 32-bit integer IFF + * that's the maximum size the file system can handle and there is no + * 64-bit type available. The rsync source must therefore take steps + * to ensure that any code that really requires a 64-bit integer has + * it (e.g. the checksum code uses two 32-bit integers for its 64-bit + * counter). */ +#if SIZEOF_INT64_T == 8 +# define int64 int64_t +# define SIZEOF_INT64 8 +#elif SIZEOF_LONG == 8 +# define int64 long +# define SIZEOF_INT64 8 +#elif SIZEOF_INT == 8 +# define int64 int +# define SIZEOF_INT64 8 +#elif SIZEOF_LONG_LONG == 8 +# define int64 long long +# define SIZEOF_INT64 8 +#elif SIZEOF_OFF64_T == 8 +# define int64 off64_t +# define SIZEOF_INT64 8 +#elif SIZEOF_OFF_T == 8 +# define int64 off_t +# define SIZEOF_INT64 8 +#elif SIZEOF_INT > 8 +# define int64 int +# define SIZEOF_INT64 SIZEOF_INT +#elif SIZEOF_LONG > 8 +# define int64 long +# define SIZEOF_INT64 SIZEOF_LONG +#elif SIZEOF_LONG_LONG > 8 +# define int64 long long +# define SIZEOF_INT64 SIZEOF_LONG_LONG +#else +/* As long as it gets... */ +# define int64 off_t +# define SIZEOF_INT64 SIZEOF_OFF_T +#endif + +struct hashtable { + void *nodes; + int32 size, entries; + uint32 node_size; + short key64; +}; + +struct ht_int32_node { + void *data; + int32 key; +}; + +struct ht_int64_node { + void *data; + int64 key; +}; + +#define HT_NODE(tbl, bkts, i) ((void*)((char*)(bkts) + (i)*(tbl)->node_size)) +#define HT_KEY(node, k64) ((k64)? ((struct ht_int64_node*)(node))->key \ + : (int64)((struct ht_int32_node*)(node))->key) + +#ifndef MIN +#define MIN(a,b) ((a)<(b)?(a):(b)) +#endif + +#ifndef MAX +#define MAX(a,b) ((a)>(b)?(a):(b)) +#endif + +#ifndef MAXHOSTNAMELEN +#define MAXHOSTNAMELEN 256 +#endif + +#define SUM_LENGTH 16 +#define SHORT_SUM_LENGTH 2 +#define BLOCKSUM_BIAS 10 + +#ifndef MAXPATHLEN +#define MAXPATHLEN 1024 +#endif + +/* We want a roomy line buffer that can hold more than MAXPATHLEN, + * and significantly more than an overly short MAXPATHLEN. */ +#if MAXPATHLEN < 4096 +#define BIGPATHBUFLEN (4096+1024) +#else +#define BIGPATHBUFLEN (MAXPATHLEN+1024) +#endif + +#ifndef NAME_MAX +#define NAME_MAX 255 +#endif + +#ifndef INADDR_NONE +#define INADDR_NONE 0xffffffff +#endif + +#ifndef IN_LOOPBACKNET +#define IN_LOOPBACKNET 127 +#endif + +#if HAVE_UNIXWARE_ACLS|HAVE_SOLARIS_ACLS|HAVE_HPUX_ACLS +#define ACLS_NEED_MASK 1 +#endif + +#if defined HAVE_FALLOCATE || HAVE_SYS_FALLOCATE +#ifdef HAVE_LINUX_FALLOC_H +#include +#endif +#ifdef FALLOC_FL_KEEP_SIZE +#define SUPPORT_PREALLOCATION 1 +#elif defined HAVE_FTRUNCATE +#define SUPPORT_PREALLOCATION 1 +#define PREALLOCATE_NEEDS_TRUNCATE 1 +#endif +#else /* !fallocate */ +#if defined HAVE_EFFICIENT_POSIX_FALLOCATE && defined HAVE_FTRUNCATE +#define SUPPORT_PREALLOCATION 1 +#define PREALLOCATE_NEEDS_TRUNCATE 1 +#endif +#endif + +union file_extras { + int32 num; + uint32 unum; +}; + +struct file_struct { + const char *dirname; /* The dir info inside the transfer */ + time_t modtime; /* When the item was last modified */ + uint32 len32; /* Lowest 32 bits of the file's length */ + uint16 mode; /* The item's type and permissions */ + uint16 flags; /* The FLAG_* bits for this item */ + const char basename[1]; /* The basename (AKA filename) follows */ +}; + +extern int file_extra_cnt; +extern int inc_recurse; +extern int uid_ndx; +extern int gid_ndx; +extern int acls_ndx; +extern int xattrs_ndx; + +#define FILE_STRUCT_LEN (offsetof(struct file_struct, basename)) +#define EXTRA_LEN (sizeof (union file_extras)) +#define PTR_EXTRA_CNT ((sizeof (char *) + EXTRA_LEN - 1) / EXTRA_LEN) +#define DEV_EXTRA_CNT 2 +#define DIRNODE_EXTRA_CNT 3 +#define SUM_EXTRA_CNT ((MAX_DIGEST_LEN + EXTRA_LEN - 1) / EXTRA_LEN) + +#define REQ_EXTRA(f,ndx) ((union file_extras*)(f) - (ndx)) +#define OPT_EXTRA(f,bump) ((union file_extras*)(f) - file_extra_cnt - 1 - (bump)) + +#define NSEC_BUMP(f) ((f)->flags & FLAG_MOD_NSEC ? 1 : 0) +#define LEN64_BUMP(f) ((f)->flags & FLAG_LENGTH64 ? 1 : 0) +#define START_BUMP(f) (NSEC_BUMP(f) + LEN64_BUMP(f)) +#define HLINK_BUMP(f) ((f)->flags & (FLAG_HLINKED|FLAG_HLINK_DONE) ? inc_recurse+1 : 0) +#define ACL_BUMP(f) (acls_ndx ? 1 : 0) + +/* The length applies to all items. */ +#if SIZEOF_INT64 < 8 +#define F_LENGTH(f) ((int64)(f)->len32) +#else +#define F_LENGTH(f) ((int64)(f)->len32 + ((f)->flags & FLAG_LENGTH64 \ + ? (int64)OPT_EXTRA(f, NSEC_BUMP(f))->unum << 32 : 0)) +#endif + +#define F_MOD_NSEC(f) ((f)->flags & FLAG_MOD_NSEC ? OPT_EXTRA(f, 0)->unum : 0) + +/* If there is a symlink string, it is always right after the basename */ +#define F_SYMLINK(f) ((f)->basename + strlen((f)->basename) + 1) + +/* The sending side always has this available: */ +#define F_PATHNAME(f) (*(const char**)REQ_EXTRA(f, PTR_EXTRA_CNT)) + +/* The receiving side always has this available: */ +#define F_DEPTH(f) REQ_EXTRA(f, 1)->num + +/* When the associated option is on, all entries will have these present: */ +#define F_OWNER(f) REQ_EXTRA(f, uid_ndx)->unum +#define F_GROUP(f) REQ_EXTRA(f, gid_ndx)->unum +#define F_ACL(f) REQ_EXTRA(f, acls_ndx)->num +#define F_XATTR(f) REQ_EXTRA(f, xattrs_ndx)->num +#define F_NDX(f) REQ_EXTRA(f, unsort_ndx)->num + +/* These items are per-entry optional: */ +#define F_HL_GNUM(f) OPT_EXTRA(f, START_BUMP(f))->num /* non-dirs */ +#define F_HL_PREV(f) OPT_EXTRA(f, START_BUMP(f)+inc_recurse)->num /* non-dirs */ +#define F_DIR_NODE_P(f) (&OPT_EXTRA(f, START_BUMP(f) \ + + DIRNODE_EXTRA_CNT - 1)->num) /* sender dirs */ +#define F_DIR_RELNAMES_P(f) (&OPT_EXTRA(f, START_BUMP(f) + DIRNODE_EXTRA_CNT \ + + PTR_EXTRA_CNT - 1)->num) /* sender dirs */ +#define F_DIR_DEFACL(f) OPT_EXTRA(f, START_BUMP(f))->unum /* receiver dirs */ +#define F_DIR_DEV_P(f) (&OPT_EXTRA(f, START_BUMP(f) + ACL_BUMP(f) \ + + DEV_EXTRA_CNT - 1)->unum) /* receiver dirs */ + +/* This optional item might follow an F_HL_*() item. */ +#define F_RDEV_P(f) (&OPT_EXTRA(f, START_BUMP(f) + HLINK_BUMP(f) + DEV_EXTRA_CNT - 1)->unum) + +/* The sum is only present on regular files. */ +#define F_SUM(f) ((char*)OPT_EXTRA(f, START_BUMP(f) + HLINK_BUMP(f) \ + + SUM_EXTRA_CNT - 1)) + +/* Some utility defines: */ +#define F_IS_ACTIVE(f) (f)->basename[0] +#define F_IS_HLINKED(f) ((f)->flags & FLAG_HLINKED) + +#define F_HLINK_NOT_FIRST(f) BITS_SETnUNSET((f)->flags, FLAG_HLINKED, FLAG_HLINK_FIRST) +#define F_HLINK_NOT_LAST(f) BITS_SETnUNSET((f)->flags, FLAG_HLINKED, FLAG_HLINK_LAST) + +/* These access the F_DIR_DEV_P() and F_RDEV_P() values: */ +#define DEV_MAJOR(a) (a)[0] +#define DEV_MINOR(a) (a)[1] + +/* These access the F_DIRS_NODE_P() values: */ +#define DIR_PARENT(a) (a)[0] +#define DIR_FIRST_CHILD(a) (a)[1] +#define DIR_NEXT_SIBLING(a) (a)[2] + +#define IS_MISSING_FILE(statbuf) ((statbuf).st_mode == 0) + +/* + * Start the flist array at FLIST_START entries and grow it + * by doubling until FLIST_LINEAR then grow by FLIST_LINEAR + */ +#define FLIST_START (32 * 1024) +#define FLIST_LINEAR (FLIST_START * 512) + +/* + * Extent size for allocation pools: A minimum size of 128KB + * is needed to mmap them so that freeing will release the + * space to the OS. + * + * Larger sizes reduce leftover fragments and speed free calls + * (when they happen). Smaller sizes increase the chance of + * freed allocations freeing whole extents. + */ +#define NORMAL_EXTENT (256 * 1024) +#define SMALL_EXTENT (128 * 1024) + +#define FLIST_TEMP (1<<1) + +struct file_list { + struct file_list *next, *prev; + struct file_struct **files, **sorted; + alloc_pool_t file_pool; + void *pool_boundary; + int used, malloced; + int low, high; /* 0-relative index values excluding empties */ + int ndx_start; /* the start offset for inc_recurse mode */ + int flist_num; /* 1-relative file_list number or 0 */ + int parent_ndx; /* dir_flist index of parent directory */ + int in_progress, to_redo; +}; + +#define SUMFLG_SAME_OFFSET (1<<0) + +struct sum_buf { + OFF_T offset; /**< offset in file of this chunk */ + int32 len; /**< length of chunk of file */ + uint32 sum1; /**< simple checksum */ + int32 chain; /**< next hash-table collision */ + short flags; /**< flag bits */ + char sum2[SUM_LENGTH]; /**< checksum */ +}; + +struct sum_struct { + OFF_T flength; /**< total file length */ + struct sum_buf *sums; /**< points to info for each chunk */ + int32 count; /**< how many chunks */ + int32 blength; /**< block_length */ + int32 remainder; /**< flength % block_length */ + int s2length; /**< sum2_length */ +}; + +struct map_struct { + OFF_T file_size; /* File size (from stat) */ + OFF_T p_offset; /* Window start */ + OFF_T p_fd_offset; /* offset of cursor in fd ala lseek */ + char *p; /* Window pointer */ + int32 p_size; /* Largest window size we allocated */ + int32 p_len; /* Latest (rounded) window size */ + int32 def_window_size; /* Default window size */ + int fd; /* File Descriptor */ + int status; /* first errno from read errors */ +}; + +#define FILTRULE_WILD (1<<0) /* pattern has '*', '[', and/or '?' */ +#define FILTRULE_WILD2 (1<<1) /* pattern has '**' */ +#define FILTRULE_WILD2_PREFIX (1<<2) /* pattern starts with "**" */ +#define FILTRULE_WILD3_SUFFIX (1<<3) /* pattern ends with "***" */ +#define FILTRULE_ABS_PATH (1<<4) /* path-match on absolute path */ +#define FILTRULE_INCLUDE (1<<5) /* this is an include, not an exclude */ +#define FILTRULE_DIRECTORY (1<<6) /* this matches only directories */ +#define FILTRULE_WORD_SPLIT (1<<7) /* split rules on whitespace */ +#define FILTRULE_NO_INHERIT (1<<8) /* don't inherit these rules */ +#define FILTRULE_NO_PREFIXES (1<<9) /* parse no prefixes from patterns */ +#define FILTRULE_MERGE_FILE (1<<10)/* specifies a file to merge */ +#define FILTRULE_PERDIR_MERGE (1<<11)/* merge-file is searched per-dir */ +#define FILTRULE_EXCLUDE_SELF (1<<12)/* merge-file name should be excluded */ +#define FILTRULE_FINISH_SETUP (1<<13)/* per-dir merge file needs setup */ +#define FILTRULE_NEGATE (1<<14)/* rule matches when pattern does not */ +#define FILTRULE_CVS_IGNORE (1<<15)/* rule was -C or :C */ +#define FILTRULE_SENDER_SIDE (1<<16)/* rule applies to the sending side */ +#define FILTRULE_RECEIVER_SIDE (1<<17)/* rule applies to the receiving side */ +#define FILTRULE_CLEAR_LIST (1<<18)/* this item is the "!" token */ +#define FILTRULE_PERISHABLE (1<<19)/* perishable if parent dir goes away */ + +#define FILTRULES_SIDES (FILTRULE_SENDER_SIDE | FILTRULE_RECEIVER_SIDE) + +typedef struct filter_struct { + struct filter_struct *next; + char *pattern; + uint32 rflags; + union { + int slash_cnt; + struct filter_list_struct *mergelist; + } u; +} filter_rule; + +typedef struct filter_list_struct { + filter_rule *head; + filter_rule *tail; + filter_rule *parent_dirscan_head; + char *debug_type; +} filter_rule_list; + +struct stats { + int64 total_size; + int64 total_transferred_size; + int64 total_written; + int64 total_read; + int64 literal_data; + int64 matched_data; + int64 flist_buildtime; + int64 flist_xfertime; + int64 flist_size; + int num_files, num_dirs, num_symlinks, num_devices, num_specials; + int created_files, created_dirs, created_symlinks, created_devices, created_specials; + int deleted_files, deleted_dirs, deleted_symlinks, deleted_devices, deleted_specials; + int xferred_files; +}; + +struct chmod_mode_struct; + +struct flist_ndx_item { + struct flist_ndx_item *next; + int ndx; +}; + +typedef struct { + struct flist_ndx_item *head, *tail; +} flist_ndx_list; + +#define EMPTY_ITEM_LIST {NULL, 0, 0} + +typedef struct { + void *items; + size_t count; + size_t malloced; +} item_list; + +#define EXPAND_ITEM_LIST(lp, type, incr) \ + (type*)expand_item_list(lp, sizeof (type), #type, incr) + +#define EMPTY_XBUF {NULL, 0, 0, 0} + +typedef struct { + char *buf; + size_t pos; /* pos = read pos in the buf */ + size_t len; /* len = chars following pos */ + size_t size; /* size = total space in buf */ +} xbuf; + +#define INIT_XBUF(xb, str, ln, sz) (xb).buf = (str), (xb).len = (ln), (xb).size = (sz), (xb).pos = 0 +#define INIT_XBUF_STRLEN(xb, str) (xb).buf = (str), (xb).len = strlen((xb).buf), (xb).size = (size_t)-1, (xb).pos = 0 +/* This one is used to make an output xbuf based on a char[] buffer: */ +#define INIT_CONST_XBUF(xb, bf) (xb).buf = (bf), (xb).size = sizeof (bf), (xb).len = (xb).pos = 0 + +#define ICB_EXPAND_OUT (1<<0) +#define ICB_INCLUDE_BAD (1<<1) +#define ICB_INCLUDE_INCOMPLETE (1<<2) +#define ICB_CIRCULAR_OUT (1<<3) +#define ICB_INIT (1<<4) + +#define IOBUF_KEEP_BUFS 0 +#define IOBUF_FREE_BUFS 1 + +#define MPLX_SWITCHING IOBUF_KEEP_BUFS +#define MPLX_ALL_DONE IOBUF_FREE_BUFS +#define MPLX_TO_BUFFERED 2 + +#define RL_EOL_NULLS (1<<0) +#define RL_DUMP_COMMENTS (1<<1) +#define RL_CONVERT (1<<2) + +typedef struct { + char name_type; + char fname[1]; /* has variable size */ +} relnamecache; + +#include "byteorder.h" +#include "lib/mdigest.h" +#include "lib/wildmatch.h" +#include "lib/permstring.h" +#include "lib/addrinfo.h" + +#ifndef __GNUC__ +#define __attribute__(x) +#else +# if __GNUC__ <= 2 +# define NORETURN +# endif +#endif + +#define UNUSED(x) x __attribute__((__unused__)) +#ifndef NORETURN +#define NORETURN __attribute__((__noreturn__)) +#endif + +typedef struct { + STRUCT_STAT st; +#ifdef SUPPORT_ACLS + struct rsync_acl *acc_acl; /* access ACL */ + struct rsync_acl *def_acl; /* default ACL */ +#endif +#ifdef SUPPORT_XATTRS + item_list *xattr; +#endif +} stat_x; + +#define ACL_READY(sx) ((sx).acc_acl != NULL) +#define XATTR_READY(sx) ((sx).xattr != NULL) + +#include "proto.h" + +#ifndef SUPPORT_XATTRS +#define x_stat(fn,fst,xst) do_stat(fn,fst) +#define x_lstat(fn,fst,xst) do_lstat(fn,fst) +#define x_fstat(fd,fst,xst) do_fstat(fd,fst) +#endif + +/* We have replacement versions of these if they're missing. */ +#ifndef HAVE_ASPRINTF +int asprintf(char **ptr, const char *format, ...); +#endif + +#ifndef HAVE_VASPRINTF +int vasprintf(char **ptr, const char *format, va_list ap); +#endif + +#if !defined HAVE_VSNPRINTF || !defined HAVE_C99_VSNPRINTF +#define vsnprintf rsync_vsnprintf +int vsnprintf(char *str, size_t count, const char *fmt, va_list args); +#endif + +#if !defined HAVE_SNPRINTF || !defined HAVE_C99_VSNPRINTF +#define snprintf rsync_snprintf +int snprintf(char *str, size_t count, const char *fmt,...); +#endif + + +#ifndef HAVE_STRERROR +extern char *sys_errlist[]; +#define strerror(i) sys_errlist[i] +#endif + +#ifndef HAVE_STRCHR +# define strchr index +# define strrchr rindex +#endif + +#ifndef HAVE_ERRNO_DECL +extern int errno; +#endif + +#ifdef HAVE_READLINK +#define SUPPORT_LINKS 1 +#if !defined NO_SYMLINK_XATTRS && !defined NO_SYMLINK_USER_XATTRS +#define do_readlink(path, buf, bufsiz) readlink(path, buf, bufsiz) +#endif +#endif +#ifdef HAVE_LINK +#define SUPPORT_HARD_LINKS 1 +#endif + +#ifdef HAVE_SIGACTION +#define SIGACTION(n,h) sigact.sa_handler=(h), sigaction((n),&sigact,NULL) +#define signal(n,h) we_need_to_call_SIGACTION_not_signal(n,h) +#else +#define SIGACTION(n,h) signal(n,h) +#endif + +#ifndef EWOULDBLOCK +#define EWOULDBLOCK EAGAIN +#endif + +#ifndef STDIN_FILENO +#define STDIN_FILENO 0 +#endif + +#ifndef STDOUT_FILENO +#define STDOUT_FILENO 1 +#endif + +#ifndef STDERR_FILENO +#define STDERR_FILENO 2 +#endif + +#ifndef S_IRUSR +#define S_IRUSR 0400 +#endif + +#ifndef S_IWUSR +#define S_IWUSR 0200 +#endif + +#ifndef ACCESSPERMS +#define ACCESSPERMS 0777 +#endif + +#ifndef S_ISVTX +#define S_ISVTX 0 +#endif + +#define CHMOD_BITS (S_ISUID | S_ISGID | S_ISVTX | ACCESSPERMS) + +#ifndef _S_IFMT +#define _S_IFMT 0170000 +#endif + +#ifndef _S_IFLNK +#define _S_IFLNK 0120000 +#endif + +#ifndef S_ISLNK +#define S_ISLNK(mode) (((mode) & (_S_IFMT)) == (_S_IFLNK)) +#endif + +#ifndef S_ISBLK +#define S_ISBLK(mode) (((mode) & (_S_IFMT)) == (_S_IFBLK)) +#endif + +#ifndef S_ISCHR +#define S_ISCHR(mode) (((mode) & (_S_IFMT)) == (_S_IFCHR)) +#endif + +#ifndef S_ISSOCK +#ifdef _S_IFSOCK +#define S_ISSOCK(mode) (((mode) & (_S_IFMT)) == (_S_IFSOCK)) +#else +#define S_ISSOCK(mode) (0) +#endif +#endif + +#ifndef S_ISFIFO +#ifdef _S_IFIFO +#define S_ISFIFO(mode) (((mode) & (_S_IFMT)) == (_S_IFIFO)) +#else +#define S_ISFIFO(mode) (0) +#endif +#endif + +#ifndef S_ISDIR +#define S_ISDIR(mode) (((mode) & (_S_IFMT)) == (_S_IFDIR)) +#endif + +#ifndef S_ISREG +#define S_ISREG(mode) (((mode) & (_S_IFMT)) == (_S_IFREG)) +#endif + +/* work out what fcntl flag to use for non-blocking */ +#ifdef O_NONBLOCK +# define NONBLOCK_FLAG O_NONBLOCK +#elif defined SYSV +# define NONBLOCK_FLAG O_NDELAY +#else +# define NONBLOCK_FLAG FNDELAY +#endif + +#ifndef INADDR_LOOPBACK +#define INADDR_LOOPBACK 0x7f000001 +#endif + +#ifndef INADDR_NONE +#define INADDR_NONE 0xffffffff +#endif + +#define IS_SPECIAL(mode) (S_ISSOCK(mode) || S_ISFIFO(mode)) +#define IS_DEVICE(mode) (S_ISCHR(mode) || S_ISBLK(mode)) + +#define PRESERVE_FILE_TIMES (1<<0) +#define PRESERVE_DIR_TIMES (1<<1) +#define PRESERVE_LINK_TIMES (1<<2) + +/* Initial mask on permissions given to temporary files. Mask off setuid + bits and group access because of potential race-condition security + holes, and mask other access because mode 707 is bizarre */ +#define INITACCESSPERMS 0700 + +/* handler for null strings in printf format */ +#define NS(s) ((s)?(s):"") + +/* Convenient wrappers for malloc and realloc. Use them. */ +#define new(type) ((type*)malloc(sizeof (type))) +#define new0(type) ((type*)calloc(1, sizeof (type))) +#define new_array(type, num) ((type*)_new_array((num), sizeof (type), 0)) +#define new_array0(type, num) ((type*)_new_array((num), sizeof (type), 1)) +#define realloc_array(ptr, type, num) ((type*)_realloc_array((ptr), sizeof(type), (num))) + +/* use magic gcc attributes to catch format errors */ + void rprintf(enum logcode , const char *, ...) + __attribute__((format (printf, 2, 3))) +; + +/* This is just like rprintf, but it also tries to print some + * representation of the error code. Normally errcode = errno. */ +void rsyserr(enum logcode, int, const char *, ...) + __attribute__((format (printf, 3, 4))) + ; + +/* Make sure that the O_BINARY flag is defined. */ +#ifndef O_BINARY +#define O_BINARY 0 +#endif + +#ifndef HAVE_STRLCPY +size_t strlcpy(char *d, const char *s, size_t bufsize); +#endif + +#ifndef HAVE_STRLCAT +size_t strlcat(char *d, const char *s, size_t bufsize); +#endif + +#ifndef WEXITSTATUS +#define WEXITSTATUS(stat) ((int)(((stat)>>8)&0xFF)) +#endif +#ifndef WIFEXITED +#define WIFEXITED(stat) ((int)((stat)&0xFF) == 0) +#endif + +#define exit_cleanup(code) _exit_cleanup(code, __FILE__, __LINE__) + +#ifdef HAVE_GETEUID +#define MY_UID() geteuid() +#else +#define MY_UID() getuid() +#endif + +#ifdef HAVE_GETEGID +#define MY_GID() getegid() +#else +#define MY_GID() getgid() +#endif + +#ifdef FORCE_FD_ZERO_MEMSET +#undef FD_ZERO +#define FD_ZERO(fdsetp) memset(fdsetp, 0, sizeof (fd_set)) +#endif + +extern short info_levels[], debug_levels[]; + +#define INFO_GTE(flag, lvl) (info_levels[INFO_##flag] >= (lvl)) +#define INFO_EQ(flag, lvl) (info_levels[INFO_##flag] == (lvl)) +#define DEBUG_GTE(flag, lvl) (debug_levels[DEBUG_##flag] >= (lvl)) +#define DEBUG_EQ(flag, lvl) (debug_levels[DEBUG_##flag] == (lvl)) + +#define INFO_BACKUP 0 +#define INFO_COPY (INFO_BACKUP+1) +#define INFO_DEL (INFO_COPY+1) +#define INFO_FLIST (INFO_DEL+1) +#define INFO_MISC (INFO_FLIST+1) +#define INFO_MOUNT (INFO_MISC+1) +#define INFO_NAME (INFO_MOUNT+1) +#define INFO_PROGRESS (INFO_NAME+1) +#define INFO_REMOVE (INFO_PROGRESS+1) +#define INFO_SKIP (INFO_REMOVE+1) +#define INFO_STATS (INFO_SKIP+1) +#define INFO_SYMSAFE (INFO_STATS+1) + +#define COUNT_INFO (INFO_SYMSAFE+1) + +#define DEBUG_ACL 0 +#define DEBUG_BACKUP (DEBUG_ACL+1) +#define DEBUG_BIND (DEBUG_BACKUP+1) +#define DEBUG_CHDIR (DEBUG_BIND+1) +#define DEBUG_CONNECT (DEBUG_CHDIR+1) +#define DEBUG_CMD (DEBUG_CONNECT+1) +#define DEBUG_DEL (DEBUG_CMD+1) +#define DEBUG_DELTASUM (DEBUG_DEL+1) +#define DEBUG_DUP (DEBUG_DELTASUM+1) +#define DEBUG_EXIT (DEBUG_DUP+1) +#define DEBUG_FILTER (DEBUG_EXIT+1) +#define DEBUG_FLIST (DEBUG_FILTER+1) +#define DEBUG_FUZZY (DEBUG_FLIST+1) +#define DEBUG_GENR (DEBUG_FUZZY+1) +#define DEBUG_HASH (DEBUG_GENR+1) +#define DEBUG_HLINK (DEBUG_HASH+1) +#define DEBUG_ICONV (DEBUG_HLINK+1) +#define DEBUG_IO (DEBUG_ICONV+1) +#define DEBUG_OWN (DEBUG_IO+1) +#define DEBUG_PROTO (DEBUG_OWN+1) +#define DEBUG_RECV (DEBUG_PROTO+1) +#define DEBUG_SEND (DEBUG_RECV+1) +#define DEBUG_TIME (DEBUG_SEND+1) + +#define COUNT_DEBUG (DEBUG_TIME+1) + +#ifndef HAVE_INET_NTOP +const char *inet_ntop(int af, const void *src, char *dst, size_t size); +#endif + +#ifndef HAVE_INET_PTON +int inet_pton(int af, const char *src, void *dst); +#endif + +#ifndef HAVE_GETPASS +char *getpass(const char *prompt); +#endif + +#ifdef MAINTAINER_MODE +const char *get_panic_action(void); +#endif diff --git a/rsync/rsync3.txt b/rsync/rsync3.txt new file mode 100644 index 0000000..967aa4b --- /dev/null +++ b/rsync/rsync3.txt @@ -0,0 +1,469 @@ +-*- indented-text -*- + +Notes towards a new version of rsync +Martin Pool , September 2001. + + +Good things about the current implementation: + + - Widely known and adopted. + + - Fast/efficient, especially for moderately small sets of files over + slow links (transoceanic or modem.) + + - Fairly reliable. + + - The choice of running over a plain TCP socket or tunneling over + ssh. + + - rsync operations are idempotent: you can always run the same + command twice to make sure it worked properly without any fear. + (Are there any exceptions?) + + - Small changes to files cause small deltas. + + - There is a way to evolve the protocol to some extent. + + - rdiff and rsync --write-batch allow generation of standalone patch + sets. rsync+ is pretty cheesy, though. xdelta seems cleaner. + + - Process triangle is creative, but seems to provoke OS bugs. + + - "Morning-after property": you don't need to know anything on the + local machine about the state of the remote machine, or about + transfers that have been done in the past. + + - You can easily push or pull simply by switching the order of + files. + + - The "modules" system has some neat features compared to + e.g. Apache's per-directory configuration. In particular, because + you can set a userid and chroot directory, there is strong + protection between different modules. I haven't seen any calls + for a more flexible system. + + +Bad things about the current implementation: + + - Persistent and hard-to-diagnose hang bugs remain + + - Protocol is sketchily documented, tied to this implementation, and + hard to modify/extend + + - Both the program and the protocol assume a single non-interactive + one-way transfer + + - A list of all files are held in memory for the entire transfer, + which cripples scalability to large file trees + + - Opening a new socket for every operation causes problems, + especially when running over SSH with password authentication. + + - Renamed files are not handled: the old file is removed, and the + new file created from scratch. + + - The versioning approach assumes that future versions of the + program know about all previous versions, and will do the right + thing. + + - People always get confused about ':' vs '::' + + - Error messages can be cryptic. + + - Default behaviour is not intuitive: in too many cases rsync will + happily do nothing. Perhaps -a should be the default? + + - People get confused by trailing slashes, though it's hard to think + of another reasonable way to make this necessary distinction + between a directory and its contents. + + +Protocol philosophy: + + *The* big difference between protocols like HTTP, FTP, and NFS is + that their fundamental operations are "read this file", "delete + this file", and "make this directory", whereas rsync is "make this + directory like this one". + + +Questionable features: + + These are neat, but not necessarily clean or worth preserving. + + - The remote rsync can be wrapped by some other program, such as in + tridge's rsync-mail scripts. The general feature of sending and + retrieving mail over rsync is good, but this is perhaps not the + right way to implement it. + + +Desirable features: + + These don't really require architectural changes; they're just + something to keep in mind. + + - Synchronize ACLs and extended attributes + + - Anonymous servers should be efficient + + - Code should be portable to non-UNIX systems + + - Should be possible to document the protocol in RFC form + + - --dry-run option + + - IPv6 support. Pretty straightforward. + + - Allow the basis and destination files to be different. For + example, you could use this when you have a CD-ROM and want to + download an updated image onto a hard drive. + + - Efficiently interrupt and restart a transfer. We can write a + checkpoint file that says where we're up to in the filesystem. + Alternatively, as long as transfers are idempotent, we can just + restart the whole thing. [NFSv4] + + - Scripting support. + + - Propagate atimes and do not modify them. This is very ugly on + Unix. It might be better to try to add O_NOATIME to kernels, and + call that. + + - Unicode. Probably just use UTF-8 for everything. + + - Open authentication system. Can we use PAM? Is SASL an adequate + mapping of PAM to the network, or useful in some other way? + + - Resume interrupted transfers without the --partial flag. We need + to leave the temporary file behind, and then know to use it. This + leaves a risk of large temporary files accumulating, which is not + good. Perhaps it should be off by default. + + - tcpwrappers support. Should be trivial; can already be done + through tcpd or inetd. + + - Socks support built in. It's not clear this is any better than + just linking against the socks library, though. + + - When run over SSH, invoke with predictable command-line arguments, + so that people can restrict what commands sshd will run. (Is this + really required?) + + - Comparison mode: give a list of which files are new, gone, or + different. Set return code depending on whether anything has + changed. + + - Internationalized messages (gettext?) + + - Optionally use real regexps rather than globs? + + - Show overall progress. Pretty hard to do, especially if we insist + on not scanning the directory tree up front. + + +Regression testing: + + - Support automatic testing. + + - Have hard internal timeouts against hangs. + + - Be deterministic. + + - Measure performance. + + +Hard links: + + At the moment, we can recreate hard links, but it's a bit + inefficient: it depends on holding a list of all files in the tree. + Every time we see a file with a linkcount >1, we need to search for + another known name that has the same (fsid,inum) tuple. We could do + that more efficiently by keeping a list of only files with + linkcount>1, and removing files from that list as all their names + become known. + + +Command-line options: + + We have rather a lot at the moment. We might get more if the tool + becomes more flexible. Do we need a .rc or configuration file? + That wouldn't really fit with its pattern of use: cp and tar don't + have them, though ssh does. + + +Scripting issues: + + - Perhaps support multiple scripting languages: candidates include + Perl, Python, Tcl, Scheme (guile?), sh, ... + + - Simply running a subprocess and looking at its stdout/exit code + might be sufficient, though it could also be pretty slow if it's + called often. + + - There are security issues about running remote code, at least if + it's not running in the users own account. So we can either + disallow it, or use some kind of sandbox system. + + - Python is a good language, but the syntax is not so good for + giving small fragments on the command line. + + - Tcl is broken Lisp. + + - Lots of sysadmins know Perl, though Perl can give some bizarre or + confusing errors. The built in stat operators and regexps might + be useful. + + - Sadly probably not enough people know Scheme. + + - sh is hard to embed. + + +Scripting hooks: + + - Whether to transfer a file + + - What basis file to use + + - Logging + + - Whether to allow transfers (for public servers) + + - Authentication + + - Locking + + - Cache + + - Generating backup path/name. + + - Post-processing of backups, e.g. to do compression. + + - After transfer, before replacement: so that we can spit out a diff + of what was changed, or kick off some kind of reconciliation + process. + + +VFS: + + Rather than talking straight to the filesystem, rsyncd talks through + an internal API. Samba has one. Is it useful? + + - Could be a tidy way to implement cached signatures. + + - Keep files compressed on disk? + + +Interactive interface: + + - Something like ncFTP, or integration into GNOME-vfs. Probably + hold a single socket connection open. + + - Can either call us as a separate process, or as a library. + + - The standalone process needs to produce output in a form easily + digestible by a calling program, like the --emacs feature some + have. Same goes for output: rpm outputs a series of hash symbols, + which are easier for a GUI to handle than "\r30% complete" + strings. + + - Yow! emacs support. (You could probably build that already, of + course.) I'd like to be able to write a simple script on a remote + machine that rsyncs it to my workstation, edits it there, then + pushes it back up. + + +Pie-in-the-sky features: + + These might have a severe impact on the protocol, and are not + clearly in our core requirements. It looks like in many of them + having scripting hooks will allow us + + - Transport over UDP multicast. The hard part is handling multiple + destinations which have different basis files. We can look at + multicast-TFTP for inspiration. + + - Conflict resolution. Possibly general scripting support will be + sufficient. + + - Integrate with locking. It's hard to see a good general solution, + because Unix systems have several locking mechanisms, and grabbing + the lock from programs that don't expect it could cause deadlocks, + timeouts, or other problems. Scripting support might help. + + - Replicate in place, rather than to a temporary file. This is + dangerous in the case of interruption, and it also means that the + delta can't refer to blocks that have already been overwritten. + On the other hand we could semi-trivially do this at first by + simply generating a delta with no copy instructions. + + - Replicate block devices. Most of the difficulties here are to do + with replication in place, though on some systems we will also + have to do I/O on block boundaries. + + - Peer to peer features. Flavour of the year. Can we think about + ways for clients to smoothly and voluntarily become servers for + content they receive? + + - Imagine a situation where the destination has a much faster link + to the cloud than the source. In this case, Mojo Nation downloads + interleaved blocks from several slower servers. The general + situation might be a way for a master rsync process to farm out + tasks to several subjobs. In this particular case they'd need + different sockets. This might be related to multicast. + + +Unlikely features: + + - Allow remote source and destination. If this can be cleanly + designed into the protocol, perhaps with the remote machine acting + as a kind of echo, then it's good. It's uncommon enough that we + don't want to shape the whole protocol around it, though. + + In fact, in a triangle of machines there are two possibilities: + all traffic passes from remote1 to remote2 through local, or local + just sets up the transfer and then remote1 talks to remote2. FTP + supports the second but it's not clearly good. There are some + security problems with being able to instruct one machine to open + a connection to another. + + +In favour of evolving the protocol: + + - Keeping compatibility with existing rsync servers will help with + adoption and testing. + + - We should at the very least be able to fall back to the new + protocol. + + - Error handling is not so good. + + +In favour of using a new protocol: + + - Maintaining compatibility might soak up development time that + would better go into improving a new protocol. + + - If we start from scratch, it can be documented as we go, and we + can avoid design decisions that make the protocol complex or + implementation-bound. + + +Error handling: + + - Errors should come back reliably, and be clearly associated with + the particular file that caused the problem. + + - Some errors ought to cause the whole transfer to abort; some are + just warnings. If any errors have occurred, then rsync ought to + return an error. + + +Concurrency: + + - We want to keep the CPU, filesystem, and network as full as + possible as much of the time as possible. + + - We can do nonblocking network IO, but not so for disk. + + - It makes sense to on the destination be generating signatures and + applying patches at the same time. + + - Can structure this with nonblocking, threads, separate processes, + etc. + + +Uses: + + - Mirroring software distributions: + + - Synchronizing laptop and desktop + + - NFS filesystem migration/replication. See + http://www.ietf.org/proceedings/00jul/00july-133.htm#P24510_1276764 + + - Sync with PDA + + - Network backup systems + + - CVS filemover + + +Conflict resolution: + + - Requires application-specific knowledge. We want to provide + policy, rather than mechanism. + + - Possibly allowing two-way migration across a single connection + would be useful. + + +Moved files: + + - There's no trivial way to detect renamed files, especially if they + move between directories. + + - If we had a picture of the remote directory from last time on + either machine, then the inode numbers might give us a hint about + files which may have been renamed. + + - Files that are renamed and not modified can be detected by + examining the directory listing, looking for files with the same + size/date as the origin. + + +Filesystem migration: + + NFSv4 probably wants to migrate file locks, but that's not really + our problem. + + +Atomic updates: + + The NFSv4 working group wants atomic migration. Most of the + responsibility for this lies on the NFS server or OS. + + If migrating a whole tree, then we could do a nearly-atomic rename + at the end. This ties in to having separate basis and destination + files. + + There's no way in Unix to replace a whole set of files atomically. + However, if we get them all onto the destination machine and then do + the updates quickly it would greatly reduce the window. + + +Scalability: + + We should aim to work well on machines in use in a year or two. + That probably means transfers of many millions of files in one + batch, and gigabytes or terabytes of data. + + For argument's sake: at the low end, we want to sync ten files for a + total of 10kb across a 1kB/s link. At the high end, we want to sync + 1e9 files for 1TB of data across a 1GB/s link. + + On the whole CPU usage is not normally a limiting factor, if only + because running over SSH burns a lot of cycles on encryption. + + Perhaps have resource throttling without relying on rlimit. + + +Streaming: + + A big attraction of rsync is that there are few round-trip delays: + basically only one to get started, and then everything is + pipelined. This is a problem with FTP, and NFS (at least up to + v3). NFSv4 can pipeline operations, but building on that is + probably a bit complicated. + + +Related work: + + - mirror.pl http://freshmeat.net/project/mirror/ + + - ProFTPd + + - Apache + + - http://freshmeat.net/search/?site=Freshmeat&q=mirror§ion=projects + + - BitTorrent -- p2p mirroring + http://bitconjurer.org/BitTorrent/ diff --git a/rsync/rsyncd.conf.5 b/rsync/rsyncd.conf.5 new file mode 100644 index 0000000..d328846 --- /dev/null +++ b/rsync/rsyncd.conf.5 @@ -0,0 +1,1082 @@ +.TH "rsyncd.conf" "5" "22 Jun 2014" "" "" +.SH "NAME" +rsyncd.conf \- configuration file for rsync in daemon mode +.SH "SYNOPSIS" + +.PP +rsyncd.conf +.PP +.SH "DESCRIPTION" + +.PP +The rsyncd.conf file is the runtime configuration file for rsync when +run as an rsync daemon. +.PP +The rsyncd.conf file controls authentication, access, logging and +available modules. +.PP +.SH "FILE FORMAT" + +.PP +The file consists of modules and parameters. A module begins with the +name of the module in square brackets and continues until the next +module begins. Modules contain parameters of the form \(dq\&name = value\(dq\&. +.PP +The file is line\-based \-\- that is, each newline\-terminated line represents +either a comment, a module name or a parameter. +.PP +Only the first equals sign in a parameter is significant. Whitespace before +or after the first equals sign is discarded. Leading, trailing and internal +whitespace in module and parameter names is irrelevant. Leading and +trailing whitespace in a parameter value is discarded. Internal whitespace +within a parameter value is retained verbatim. +.PP +Any line \fBbeginning\fP with a hash (#) is ignored, as are lines containing +only whitespace. (If a hash occurs after anything other than leading +whitespace, it is considered a part of the line\(cq\&s content.) +.PP +Any line ending in a \e is \(dq\&continued\(dq\& on the next line in the +customary UNIX fashion. +.PP +The values following the equals sign in parameters are all either a string +(no quotes needed) or a boolean, which may be given as yes/no, 0/1 or +true/false. Case is not significant in boolean values, but is preserved +in string values. +.PP +.SH "LAUNCHING THE RSYNC DAEMON" + +.PP +The rsync daemon is launched by specifying the \fB\-\-daemon\fP option to +rsync. +.PP +The daemon must run with root privileges if you wish to use chroot, to +bind to a port numbered under 1024 (as is the default 873), or to set +file ownership. Otherwise, it must just have permission to read and +write the appropriate data, log, and lock files. +.PP +You can launch it either via inetd, as a stand\-alone daemon, or from +an rsync client via a remote shell. If run as a stand\-alone daemon then +just run the command \(dq\&\fBrsync \-\-daemon\fP\(dq\& from a suitable startup script. +.PP +When run via inetd you should add a line like this to /etc/services: +.PP +.nf + rsync 873/tcp +.fi + +.PP +and a single line something like this to /etc/inetd.conf: +.PP +.nf + rsync stream tcp nowait root /usr/bin/rsync rsyncd \-\-daemon +.fi + +.PP +Replace \(dq\&/usr/bin/rsync\(dq\& with the path to where you have rsync installed on +your system. You will then need to send inetd a HUP signal to tell it to +reread its config file. +.PP +Note that you should \fBnot\fP send the rsync daemon a HUP signal to force +it to reread the \f(CWrsyncd.conf\fP file. The file is re\-read on each client +connection. +.PP +.SH "GLOBAL PARAMETERS" + +.PP +The first parameters in the file (before a [module] header) are the +global parameters. +.PP +You may also include any module parameters in the global part of the +config file in which case the supplied value will override the +default for that parameter. +.PP +You may use references to environment variables in the values of parameters. +String parameters will have %VAR% references expanded as late as possible (when +the string is used in the program), allowing for the use of variables that +rsync sets at connection time, such as RSYNC_USER_NAME. Non\-string parameters +(such as true/false settings) are expanded when read from the config file. If +a variable does not exist in the environment, or if a sequence of characters is +not a valid reference (such as an un\-paired percent sign), the raw characters +are passed through unchanged. This helps with backward compatibility and +safety (e.g. expanding a non\-existent %VAR% to an empty string in a path could +result in a very unsafe path). The safest way to insert a literal % into a +value is to use %%. +.PP +.IP "\fBmotd file\fP" +This parameter allows you to specify a +\(dq\&message of the day\(dq\& to display to clients on each connect. This +usually contains site information and any legal notices. The default +is no motd file. +This can be overridden by the \fB\-\-dparam=motdfile=FILE\fP +command\-line option when starting the daemon. +.IP +.IP "\fBpid file\fP" +This parameter tells the rsync daemon to write +its process ID to that file. If the file already exists, the rsync +daemon will abort rather than overwrite the file. +This can be overridden by the \fB\-\-dparam=pidfile=FILE\fP +command\-line option when starting the daemon. +.IP +.IP "\fBport\fP" +You can override the default port the daemon will listen on +by specifying this value (defaults to 873). This is ignored if the daemon +is being run by inetd, and is superseded by the \fB\-\-port\fP command\-line option. +.IP +.IP "\fBaddress\fP" +You can override the default IP address the daemon +will listen on by specifying this value. This is ignored if the daemon is +being run by inetd, and is superseded by the \fB\-\-address\fP command\-line option. +.IP +.IP "\fBsocket options\fP" +This parameter can provide endless fun for people +who like to tune their systems to the utmost degree. You can set all +sorts of socket options which may make transfers faster (or +slower!). Read the man page for the +\f(CWsetsockopt()\fP +system call for +details on some of the options you may be able to set. By default no +special socket options are set. These settings can also be specified +via the \fB\-\-sockopts\fP command\-line option. +.IP +.IP "\fBlisten backlog\fP" +You can override the default backlog value when the +daemon listens for connections. It defaults to 5. +.IP +.SH "MODULE PARAMETERS" + +.PP +After the global parameters you should define a number of modules, each +module exports a directory tree as a symbolic name. Modules are +exported by specifying a module name in square brackets [module] +followed by the parameters for that module. +The module name cannot contain a slash or a closing square bracket. If the +name contains whitespace, each internal sequence of whitespace will be +changed into a single space, while leading or trailing whitespace will be +discarded. +.PP +As with GLOBAL PARAMETERS, you may use references to environment variables in +the values of parameters. See the GLOBAL PARAMETERS section for more details. +.PP +.IP "\fBcomment\fP" +This parameter specifies a description string +that is displayed next to the module name when clients obtain a list +of available modules. The default is no comment. +.IP +.IP "\fBpath\fP" +This parameter specifies the directory in the daemon\(cq\&s +filesystem to make available in this module. You must specify this parameter +for each module in \f(CWrsyncd.conf\fP. +.IP +You may base the path\(cq\&s value off of an environment variable by surrounding +the variable name with percent signs. You can even reference a variable +that is set by rsync when the user connects. +For example, this would use the authorizing user\(cq\&s name in the path: +.IP +.nf + path = /home/%RSYNC_USER_NAME% +.fi + +.IP +It is fine if the path includes internal spaces \-\- they will be retained +verbatim (which means that you shouldn\(cq\&t try to escape them). If your final +directory has a trailing space (and this is somehow not something you wish to +fix), append a trailing slash to the path to avoid losing the trailing +whitespace. +.IP +.IP "\fBuse chroot\fP" +If \(dq\&use chroot\(dq\& is true, the rsync daemon will chroot +to the \(dq\&path\(dq\& before starting the file transfer with the client. This has +the advantage of extra protection against possible implementation security +holes, but it has the disadvantages of requiring super\-user privileges, +of not being able to follow symbolic links that are either absolute or outside +of the new root path, and of complicating the preservation of users and groups +by name (see below). +.IP +As an additional safety feature, you can specify a dot\-dir in the module\(cq\&s +\(dq\&path\(dq\& to indicate the point where the chroot should occur. This allows rsync +to run in a chroot with a non\-\(dq\&/\(dq\& path for the top of the transfer hierarchy. +Doing this guards against unintended library loading (since those absolute +paths will not be inside the transfer hierarchy unless you have used an unwise +pathname), and lets you setup libraries for the chroot that are outside of the +transfer. For example, specifying \(dq\&/var/rsync/./module1\(dq\& will chroot to the +\(dq\&/var/rsync\(dq\& directory and set the inside\-chroot path to \(dq\&/module1\(dq\&. If you +had omitted the dot\-dir, the chroot would have used the whole path, and the +inside\-chroot path would have been \(dq\&/\(dq\&. +.IP +When \(dq\&use chroot\(dq\& is false or the inside\-chroot path is not \(dq\&/\(dq\&, rsync will: +(1) munge symlinks by +default for security reasons (see \(dq\&munge symlinks\(dq\& for a way to turn this +off, but only if you trust your users), (2) substitute leading slashes in +absolute paths with the module\(cq\&s path (so that options such as +\fB\-\-backup\-dir\fP, \fB\-\-compare\-dest\fP, etc. interpret an absolute path as +rooted in the module\(cq\&s \(dq\&path\(dq\& dir), and (3) trim \(dq\&..\(dq\& path elements from +args if rsync believes they would escape the module hierarchy. +The default for \(dq\&use chroot\(dq\& is true, and is the safer choice (especially +if the module is not read\-only). +.IP +When this parameter is enabled, rsync will not attempt to map users and groups +by name (by default), but instead copy IDs as though \fB\-\-numeric\-ids\fP had +been specified. In order to enable name\-mapping, rsync needs to be able to +use the standard library functions for looking up names and IDs (i.e. +\f(CWgetpwuid()\fP +, +\f(CWgetgrgid()\fP +, +\f(CWgetpwname()\fP +, and +\f(CWgetgrnam()\fP +). +This means the rsync +process in the chroot hierarchy will need to have access to the resources +used by these library functions (traditionally /etc/passwd and +/etc/group, but perhaps additional dynamic libraries as well). +.IP +If you copy the necessary resources into the module\(cq\&s chroot area, you +should protect them through your OS\(cq\&s normal user/group or ACL settings (to +prevent the rsync module\(cq\&s user from being able to change them), and then +hide them from the user\(cq\&s view via \(dq\&exclude\(dq\& (see how in the discussion of +that parameter). At that point it will be safe to enable the mapping of users +and groups by name using the \(dq\&numeric ids\(dq\& daemon parameter (see below). +.IP +Note also that you are free to setup custom user/group information in the +chroot area that is different from your normal system. For example, you +could abbreviate the list of users and groups. +.IP +.IP "\fBnumeric ids\fP" +Enabling this parameter disables the mapping +of users and groups by name for the current daemon module. This prevents +the daemon from trying to load any user/group\-related files or libraries. +This enabling makes the transfer behave as if the client had passed +the \fB\-\-numeric\-ids\fP command\-line option. By default, this parameter is +enabled for chroot modules and disabled for non\-chroot modules. +.IP +A chroot\-enabled module should not have this parameter enabled unless you\(cq\&ve +taken steps to ensure that the module has the necessary resources it needs +to translate names, and that it is not possible for a user to change those +resources. +.IP +.IP "\fBmunge symlinks\fP" +This parameter tells rsync to modify +all symlinks in the same way as the (non\-daemon\-affecting) +\fB\-\-munge\-links\fP command\-line option (using a method described below). +This should help protect your files from user trickery when +your daemon module is writable. The default is disabled when \(dq\&use chroot\(dq\& +is on and the inside\-chroot path is \(dq\&/\(dq\&, otherwise it is enabled. +.IP +If you disable this parameter on a daemon that is not read\-only, there +are tricks that a user can play with uploaded symlinks to access +daemon\-excluded items (if your module has any), and, if \(dq\&use chroot\(dq\& +is off, rsync can even be tricked into showing or changing data that +is outside the module\(cq\&s path (as access\-permissions allow). +.IP +The way rsync disables the use of symlinks is to prefix each one with +the string \(dq\&/rsyncd\-munged/\(dq\&. This prevents the links from being used +as long as that directory does not exist. When this parameter is enabled, +rsync will refuse to run if that path is a directory or a symlink to +a directory. When using the \(dq\&munge symlinks\(dq\& parameter in a chroot area +that has an inside\-chroot path of \(dq\&/\(dq\&, you should add \(dq\&/rsyncd\-munged/\(dq\& +to the exclude setting for the module so that +a user can\(cq\&t try to create it. +.IP +Note: rsync makes no attempt to verify that any pre\-existing symlinks in +the module\(cq\&s hierarchy are as safe as you want them to be (unless, of +course, it just copied in the whole hierarchy). If you setup an rsync +daemon on a new area or locally add symlinks, you can manually protect your +symlinks from being abused by prefixing \(dq\&/rsyncd\-munged/\(dq\& to the start of +every symlink\(cq\&s value. There is a perl script in the support directory +of the source code named \(dq\&munge\-symlinks\(dq\& that can be used to add or remove +this prefix from your symlinks. +.IP +When this parameter is disabled on a writable module and \(dq\&use chroot\(dq\& is off +(or the inside\-chroot path is not \(dq\&/\(dq\&), +incoming symlinks will be modified to drop a leading slash and to remove \(dq\&..\(dq\& +path elements that rsync believes will allow a symlink to escape the module\(cq\&s +hierarchy. There are tricky ways to work around this, though, so you had +better trust your users if you choose this combination of parameters. +.IP +.IP "\fBcharset\fP" +This specifies the name of the character set in which the +module\(cq\&s filenames are stored. If the client uses an \fB\-\-iconv\fP option, +the daemon will use the value of the \(dq\&charset\(dq\& parameter regardless of the +character set the client actually passed. This allows the daemon to +support charset conversion in a chroot module without extra files in the +chroot area, and also ensures that name\-translation is done in a consistent +manner. If the \(dq\&charset\(dq\& parameter is not set, the \fB\-\-iconv\fP option is +refused, just as if \(dq\&iconv\(dq\& had been specified via \(dq\&refuse options\(dq\&. +.IP +If you wish to force users to always use \fB\-\-iconv\fP for a particular +module, add \(dq\&no\-iconv\(dq\& to the \(dq\&refuse options\(dq\& parameter. Keep in mind +that this will restrict access to your module to very new rsync clients. +.IP +.IP "\fBmax connections\fP" +This parameter allows you to +specify the maximum number of simultaneous connections you will allow. +Any clients connecting when the maximum has been reached will receive a +message telling them to try later. The default is 0, which means no limit. +A negative value disables the module. +See also the \(dq\&lock file\(dq\& parameter. +.IP +.IP "\fBlog file\fP" +When the \(dq\&log file\(dq\& parameter is set to a non\-empty +string, the rsync daemon will log messages to the indicated file rather +than using syslog. This is particularly useful on systems (such as AIX) +where +\f(CWsyslog()\fP +doesn\(cq\&t work for chrooted programs. The file is +opened before +\f(CWchroot()\fP +is called, allowing it to be placed outside +the transfer. If this value is set on a per\-module basis instead of +globally, the global log will still contain any authorization failures +or config\-file error messages. +.IP +If the daemon fails to open the specified file, it will fall back to +using syslog and output an error about the failure. (Note that the +failure to open the specified log file used to be a fatal error.) +.IP +This setting can be overridden by using the \fB\-\-log\-file=FILE\fP or +\fB\-\-dparam=logfile=FILE\fP command\-line options. The former overrides +all the log\-file parameters of the daemon and all module settings. +The latter sets the daemon\(cq\&s log file and the default for all the +modules, which still allows modules to override the default setting. +.IP +.IP "\fBsyslog facility\fP" +This parameter allows you to +specify the syslog facility name to use when logging messages from the +rsync daemon. You may use any standard syslog facility name which is +defined on your system. Common names are auth, authpriv, cron, daemon, +ftp, kern, lpr, mail, news, security, syslog, user, uucp, local0, +local1, local2, local3, local4, local5, local6 and local7. The default +is daemon. This setting has no effect if the \(dq\&log file\(dq\& setting is a +non\-empty string (either set in the per\-modules settings, or inherited +from the global settings). +.IP +.IP "\fBmax verbosity\fP" +This parameter allows you to control +the maximum amount of verbose information that you\(cq\&ll allow the daemon to +generate (since the information goes into the log file). The default is 1, +which allows the client to request one level of verbosity. +.IP +This also affects the user\(cq\&s ability to request higher levels of \fB\-\-info\fP and +\fB\-\-debug\fP logging. If the max value is 2, then no info and/or debug value +that is higher than what would be set by \fB\-vv\fP will be honored by the daemon +in its logging. To see how high of a verbosity level you need to accept for a +particular info/debug level, refer to \(dq\&rsync \-\-info=help\(dq\& and \(dq\&rsync \-\-debug=help\(dq\&. +For instance, it takes max\-verbosity 4 to be able to output debug TIME2 and FLIST3. +.IP +.IP "\fBlock file\fP" +This parameter specifies the file to use to +support the \(dq\&max connections\(dq\& parameter. The rsync daemon uses record +locking on this file to ensure that the max connections limit is not +exceeded for the modules sharing the lock file. +The default is \f(CW/var/run/rsyncd.lock\fP. +.IP +.IP "\fBread only\fP" +This parameter determines whether clients +will be able to upload files or not. If \(dq\&read only\(dq\& is true then any +attempted uploads will fail. If \(dq\&read only\(dq\& is false then uploads will +be possible if file permissions on the daemon side allow them. The default +is for all modules to be read only. +.IP +Note that \(dq\&auth users\(dq\& can override this setting on a per\-user basis. +.IP +.IP "\fBwrite only\fP" +This parameter determines whether clients +will be able to download files or not. If \(dq\&write only\(dq\& is true then any +attempted downloads will fail. If \(dq\&write only\(dq\& is false then downloads +will be possible if file permissions on the daemon side allow them. The +default is for this parameter to be disabled. +.IP +.IP "\fBlist\fP" +This parameter determines whether this module is +listed when the client asks for a listing of available modules. In addition, +if this is false, the daemon will pretend the module does not exist +when a client denied by \(dq\&hosts allow\(dq\& or \(dq\&hosts deny\(dq\& attempts to access it. +Realize that if \(dq\&reverse lookup\(dq\& is disabled globally but enabled for the +module, the resulting reverse lookup to a potentially client\-controlled DNS +server may still reveal to the client that it hit an existing module. +The default is for modules to be listable. +.IP +.IP "\fBuid\fP" +This parameter specifies the user name or user ID that +file transfers to and from that module should take place as when the daemon +was run as root. In combination with the \(dq\&gid\(dq\& parameter this determines what +file permissions are available. The default when run by a super\-user is to +switch to the system\(cq\&s \(dq\&nobody\(dq\& user. The default for a non\-super\-user is to +not try to change the user. See also the \(dq\&gid\(dq\& parameter. +.IP +The RSYNC_USER_NAME environment variable may be used to request that rsync run +as the authorizing user. For example, if you want a rsync to run as the same +user that was received for the rsync authentication, this setup is useful: +.IP +.nf + uid = %RSYNC_USER_NAME% + gid = * +.fi + +.IP +.IP "\fBgid\fP" +This parameter specifies one or more group names/IDs that will be +used when accessing the module. The first one will be the default group, and +any extra ones be set as supplemental groups. You may also specify a \(dq\&*\(dq\& as +the first gid in the list, which will be replaced by all the normal groups for +the transfer\(cq\&s user (see \(dq\&uid\(dq\&). The default when run by a super\-user is to +switch to your OS\(cq\&s \(dq\&nobody\(dq\& (or perhaps \(dq\&nogroup\(dq\&) group with no other +supplementary groups. The default for a non\-super\-user is to not change any +group attributes (and indeed, your OS may not allow a non\-super\-user to try to +change their group settings). +.IP +.IP "\fBfake super\fP" +Setting \(dq\&fake super = yes\(dq\& for a module causes the +daemon side to behave as if the \fB\-\-fake\-super\fP command\-line option had +been specified. This allows the full attributes of a file to be stored +without having to have the daemon actually running as root. +.IP +.IP "\fBfilter\fP" +The daemon has its own filter chain that determines what files +it will let the client access. This chain is not sent to the client and is +independent of any filters the client may have specified. Files excluded by +the daemon filter chain (\fBdaemon\-excluded\fP files) are treated as non\-existent +if the client tries to pull them, are skipped with an error message if the +client tries to push them (triggering exit code 23), and are never deleted from +the module. You can use daemon filters to prevent clients from downloading or +tampering with private administrative files, such as files you may add to +support uid/gid name translations. +.IP +The daemon filter chain is built from the \(dq\&filter\(dq\&, \(dq\&include from\(dq\&, \(dq\&include\(dq\&, +\(dq\&exclude from\(dq\&, and \(dq\&exclude\(dq\& parameters, in that order of priority. Anchored +patterns are anchored at the root of the module. To prevent access to an +entire subtree, for example, \(dq\&/secret\(dq\&, you \fImust\fP exclude everything in the +subtree; the easiest way to do this is with a triple\-star pattern like +\(dq\&/secret/***\(dq\&. +.IP +The \(dq\&filter\(dq\& parameter takes a space\-separated list of daemon filter rules, +though it is smart enough to know not to split a token at an internal space in +a rule (e.g. \(dq\&\- /foo \- /bar\(dq\& is parsed as two rules). You may specify one or +more merge\-file rules using the normal syntax. Only one \(dq\&filter\(dq\& parameter can +apply to a given module in the config file, so put all the rules you want in a +single parameter. Note that per\-directory merge\-file rules do not provide as +much protection as global rules, but they can be used to make \fB\-\-delete\fP work +better during a client download operation if the per\-dir merge files are +included in the transfer and the client requests that they be used. +.IP +.IP "\fBexclude\fP" +This parameter takes a space\-separated list of daemon +exclude patterns. As with the client \fB\-\-exclude\fP option, patterns can be +qualified with \(dq\&\- \(dq\& or \(dq\&+ \(dq\& to explicitly indicate exclude/include. Only one +\(dq\&exclude\(dq\& parameter can apply to a given module. See the \(dq\&filter\(dq\& parameter +for a description of how excluded files affect the daemon. +.IP +.IP "\fBinclude\fP" +Use an \(dq\&include\(dq\& to override the effects of the \(dq\&exclude\(dq\& +parameter. Only one \(dq\&include\(dq\& parameter can apply to a given module. See the +\(dq\&filter\(dq\& parameter for a description of how excluded files affect the daemon. +.IP +.IP "\fBexclude from\fP" +This parameter specifies the name of a file +on the daemon that contains daemon exclude patterns, one per line. Only one +\(dq\&exclude from\(dq\& parameter can apply to a given module; if you have multiple +exclude\-from files, you can specify them as a merge file in the \(dq\&filter\(dq\& +parameter. See the \(dq\&filter\(dq\& parameter for a description of how excluded files +affect the daemon. +.IP +.IP "\fBinclude from\fP" +Analogue of \(dq\&exclude from\(dq\& for a file of daemon include +patterns. Only one \(dq\&include from\(dq\& parameter can apply to a given module. See +the \(dq\&filter\(dq\& parameter for a description of how excluded files affect the +daemon. +.IP +.IP "\fBincoming chmod\fP" +This parameter allows you to specify a set of +comma\-separated chmod strings that will affect the permissions of all +incoming files (files that are being received by the daemon). These +changes happen after all other permission calculations, and this will +even override destination\-default and/or existing permissions when the +client does not specify \fB\-\-perms\fP. +See the description of the \fB\-\-chmod\fP rsync option and the \fBchmod\fP(1) +manpage for information on the format of this string. +.IP +.IP "\fBoutgoing chmod\fP" +This parameter allows you to specify a set of +comma\-separated chmod strings that will affect the permissions of all +outgoing files (files that are being sent out from the daemon). These +changes happen first, making the sent permissions appear to be different +than those stored in the filesystem itself. For instance, you could +disable group write permissions on the server while having it appear to +be on to the clients. +See the description of the \fB\-\-chmod\fP rsync option and the \fBchmod\fP(1) +manpage for information on the format of this string. +.IP +.IP "\fBauth users\fP" +This parameter specifies a comma and/or space\-separated +list of authorization rules. In its simplest form, you list the usernames +that will be allowed to connect to +this module. The usernames do not need to exist on the local +system. The rules may contain shell wildcard characters that will be matched +against the username provided by the client for authentication. If +\(dq\&auth users\(dq\& is set then the client will be challenged to supply a +username and password to connect to the module. A challenge response +authentication protocol is used for this exchange. The plain text +usernames and passwords are stored in the file specified by the +\(dq\&secrets file\(dq\& parameter. The default is for all users to be able to +connect without a password (this is called \(dq\&anonymous rsync\(dq\&). +.IP +In addition to username matching, you can specify groupname matching via a \(cq\&@\(cq\& +prefix. When using groupname matching, the authenticating username must be a +real user on the system, or it will be assumed to be a member of no groups. +For example, specifying \(dq\&@rsync\(dq\& will match the authenticating user if the +named user is a member of the rsync group. +.IP +Finally, options may be specified after a colon (:). The options allow you to +\(dq\&deny\(dq\& a user or a group, set the access to \(dq\&ro\(dq\& (read\-only), or set the access +to \(dq\&rw\(dq\& (read/write). Setting an auth\-rule\-specific ro/rw setting overrides +the module\(cq\&s \(dq\&read only\(dq\& setting. +.IP +Be sure to put the rules in the order you want them to be matched, because the +checking stops at the first matching user or group, and that is the only auth +that is checked. For example: +.IP +.nf + auth users = joe:deny @guest:deny admin:rw @rsync:ro susan joe sam +.fi + +.IP +In the above rule, user joe will be denied access no matter what. Any user +that is in the group \(dq\&guest\(dq\& is also denied access. The user \(dq\&admin\(dq\& gets +access in read/write mode, but only if the admin user is not in group \(dq\&guest\(dq\& +(because the admin user\-matching rule would never be reached if the user is in +group \(dq\&guest\(dq\&). Any other user who is in group \(dq\&rsync\(dq\& will get read\-only +access. Finally, users susan, joe, and sam get the ro/rw setting of the +module, but only if the user didn\(cq\&t match an earlier group\-matching rule. +.IP +See the description of the secrets file for how you can have per\-user passwords +as well as per\-group passwords. It also explains how a user can authenticate +using their user password or (when applicable) a group password, depending on +what rule is being authenticated. +.IP +See also the section entitled \(dq\&USING RSYNC\-DAEMON FEATURES VIA A REMOTE +SHELL CONNECTION\(dq\& in \fBrsync\fP(1) for information on how handle an +rsyncd.conf\-level username that differs from the remote\-shell\-level +username when using a remote shell to connect to an rsync daemon. +.IP +.IP "\fBsecrets file\fP" +This parameter specifies the name of a file that contains +the username:password and/or @groupname:password pairs used for authenticating +this module. This file is only consulted if the \(dq\&auth users\(dq\& parameter is +specified. The file is line\-based and contains one name:password pair per +line. Any line has a hash (#) as the very first character on the line is +considered a comment and is skipped. The passwords can contain any characters +but be warned that many operating systems limit the length of passwords that +can be typed at the client end, so you may find that passwords longer than 8 +characters don\(cq\&t work. +.IP +The use of group\-specific lines are only relevant when the module is being +authorized using a matching \(dq\&@groupname\(dq\& rule. When that happens, the user +can be authorized via either their \(dq\&username:password\(dq\& line or the +\(dq\&@groupname:password\(dq\& line for the group that triggered the authentication. +.IP +It is up to you what kind of password entries you want to include, either +users, groups, or both. The use of group rules in \(dq\&auth users\(dq\& does not +require that you specify a group password if you do not want to use shared +passwords. +.IP +There is no default for the \(dq\&secrets file\(dq\& parameter, you must choose a name +(such as \f(CW/etc/rsyncd.secrets\fP). The file must normally not be readable +by \(dq\&other\(dq\&; see \(dq\&strict modes\(dq\&. If the file is not found or is rejected, no +logins for a \(dq\&user auth\(dq\& module will be possible. +.IP +.IP "\fBstrict modes\fP" +This parameter determines whether or not +the permissions on the secrets file will be checked. If \(dq\&strict modes\(dq\& is +true, then the secrets file must not be readable by any user ID other +than the one that the rsync daemon is running under. If \(dq\&strict modes\(dq\& is +false, the check is not performed. The default is true. This parameter +was added to accommodate rsync running on the Windows operating system. +.IP +.IP "\fBhosts allow\fP" +This parameter allows you to specify a +list of patterns that are matched against a connecting clients +hostname and IP address. If none of the patterns match then the +connection is rejected. +.IP +Each pattern can be in one of five forms: +.IP +.RS +.IP o +a dotted decimal IPv4 address of the form a.b.c.d, or an IPv6 address +of the form a:b:c::d:e:f. In this case the incoming machine\(cq\&s IP address +must match exactly. +.IP o +an address/mask in the form ipaddr/n where ipaddr is the IP address +and n is the number of one bits in the netmask. All IP addresses which +match the masked IP address will be allowed in. +.IP o +an address/mask in the form ipaddr/maskaddr where ipaddr is the +IP address and maskaddr is the netmask in dotted decimal notation for IPv4, +or similar for IPv6, e.g. ffff:ffff:ffff:ffff:: instead of /64. All IP +addresses which match the masked IP address will be allowed in. +.IP o +a hostname pattern using wildcards. If the hostname of the connecting IP +(as determined by a reverse lookup) matches the wildcarded name (using the +same rules as normal unix filename matching), the client is allowed in. This +only works if \(dq\&reverse lookup\(dq\& is enabled (the default). +.IP o +a hostname. A plain hostname is matched against the reverse DNS of the +connecting IP (if \(dq\&reverse lookup\(dq\& is enabled), and/or the IP of the given +hostname is matched against the connecting IP (if \(dq\&forward lookup\(dq\& is +enabled, as it is by default). Any match will be allowed in. +.RE + +.IP +Note IPv6 link\-local addresses can have a scope in the address specification: +.IP +.RS +\f(CW fe80::1%link1\fP +.br +\f(CW fe80::%link1/64\fP +.br +\f(CW fe80::%link1/ffff:ffff:ffff:ffff::\fP +.br +.RE + +.IP +You can also combine \(dq\&hosts allow\(dq\& with a separate \(dq\&hosts deny\(dq\& +parameter. If both parameters are specified then the \(dq\&hosts allow\(dq\& parameter is +checked first and a match results in the client being able to +connect. The \(dq\&hosts deny\(dq\& parameter is then checked and a match means +that the host is rejected. If the host does not match either the +\(dq\&hosts allow\(dq\& or the \(dq\&hosts deny\(dq\& patterns then it is allowed to +connect. +.IP +The default is no \(dq\&hosts allow\(dq\& parameter, which means all hosts can connect. +.IP +.IP "\fBhosts deny\fP" +This parameter allows you to specify a +list of patterns that are matched against a connecting clients +hostname and IP address. If the pattern matches then the connection is +rejected. See the \(dq\&hosts allow\(dq\& parameter for more information. +.IP +The default is no \(dq\&hosts deny\(dq\& parameter, which means all hosts can connect. +.IP +.IP "\fBreverse lookup\fP" +Controls whether the daemon performs a reverse lookup +on the client\(cq\&s IP address to determine its hostname, which is used for +\(dq\&hosts allow\(dq\&/\(dq\&hosts deny\(dq\& checks and the \(dq\&%h\(dq\& log escape. This is enabled by +default, but you may wish to disable it to save time if you know the lookup will +not return a useful result, in which case the daemon will use the name +\(dq\&UNDETERMINED\(dq\& instead. +.IP +If this parameter is enabled globally (even by default), rsync performs the +lookup as soon as a client connects, so disabling it for a module will not +avoid the lookup. Thus, you probably want to disable it globally and then +enable it for modules that need the information. +.IP +.IP "\fBforward lookup\fP" +Controls whether the daemon performs a forward lookup +on any hostname specified in an hosts allow/deny setting. By default this is +enabled, allowing the use of an explicit hostname that would not be returned +by reverse DNS of the connecting IP. +.IP +.IP "\fBignore errors\fP" +This parameter tells rsyncd to +ignore I/O errors on the daemon when deciding whether to run the delete +phase of the transfer. Normally rsync skips the \fB\-\-delete\fP step if any +I/O errors have occurred in order to prevent disastrous deletion due +to a temporary resource shortage or other I/O error. In some cases this +test is counter productive so you can use this parameter to turn off this +behavior. +.IP +.IP "\fBignore nonreadable\fP" +This tells the rsync daemon to completely +ignore files that are not readable by the user. This is useful for +public archives that may have some non\-readable files among the +directories, and the sysadmin doesn\(cq\&t want those files to be seen at all. +.IP +.IP "\fBtransfer logging\fP" +This parameter enables per\-file +logging of downloads and uploads in a format somewhat similar to that +used by ftp daemons. The daemon always logs the transfer at the end, so +if a transfer is aborted, no mention will be made in the log file. +.IP +If you want to customize the log lines, see the \(dq\&log format\(dq\& parameter. +.IP +.IP "\fBlog format\fP" +This parameter allows you to specify the +format used for logging file transfers when transfer logging is enabled. +The format is a text string containing embedded single\-character escape +sequences prefixed with a percent (%) character. An optional numeric +field width may also be specified between the percent and the escape +letter (e.g. \(dq\&\fB%\-50n %8l %07p\fP\(dq\&). +In addition, one or more apostrophes may be specified prior to a numerical +escape to indicate that the numerical value should be made more human\-readable. +The 3 supported levels are the same as for the \fB\-\-human\-readable\fP +command\-line option, though the default is for human\-readability to be off. +Each added apostrophe increases the level (e.g. \(dq\&\fB%'\&'\&l %'\&b %f\fP\(dq\&). +.IP +The default log format is \(dq\&%o %h [%a] %m (%u) %f %l\(dq\&, and a \(dq\&%t [%p] \(dq\& +is always prefixed when using the \(dq\&log file\(dq\& parameter. +(A perl script that will summarize this default log format is included +in the rsync source code distribution in the \(dq\&support\(dq\& subdirectory: +rsyncstats.) +.IP +The single\-character escapes that are understood are as follows: +.IP +.RS +.IP o +%a the remote IP address (only available for a daemon) +.IP o +%b the number of bytes actually transferred +.IP o +%B the permission bits of the file (e.g. rwxrwxrwt) +.IP o +%c the total size of the block checksums received for the basis file (only when sending) +.IP o +%C the full\-file MD5 checksum if \fB\-\-checksum\fP is enabled or a file was transferred (only for protocol 30 or above). +.IP o +%f the filename (long form on sender; no trailing \(dq\&/\(dq\&) +.IP o +%G the gid of the file (decimal) or \(dq\&DEFAULT\(dq\& +.IP o +%h the remote host name (only available for a daemon) +.IP o +%i an itemized list of what is being updated +.IP o +%l the length of the file in bytes +.IP o +%L the string \(dq\& \-> SYMLINK\(dq\&, \(dq\& => HARDLINK\(dq\&, or \(dq\&\(dq\& (where \fBSYMLINK\fP or \fBHARDLINK\fP is a filename) +.IP o +%m the module name +.IP o +%M the last\-modified time of the file +.IP o +%n the filename (short form; trailing \(dq\&/\(dq\& on dir) +.IP o +%o the operation, which is \(dq\&send\(dq\&, \(dq\&recv\(dq\&, or \(dq\&del.\(dq\& (the latter includes the trailing period) +.IP o +%p the process ID of this rsync session +.IP o +%P the module path +.IP o +%t the current date time +.IP o +%u the authenticated username or an empty string +.IP o +%U the uid of the file (decimal) +.RE + +.IP +For a list of what the characters mean that are output by \(dq\&%i\(dq\&, see the +\fB\-\-itemize\-changes\fP option in the rsync manpage. +.IP +Note that some of the logged output changes when talking with older +rsync versions. For instance, deleted files were only output as verbose +messages prior to rsync 2.6.4. +.IP +.IP "\fBtimeout\fP" +This parameter allows you to override the +clients choice for I/O timeout for this module. Using this parameter you +can ensure that rsync won\(cq\&t wait on a dead client forever. The timeout +is specified in seconds. A value of zero means no timeout and is the +default. A good choice for anonymous rsync daemons may be 600 (giving +a 10 minute timeout). +.IP +.IP "\fBrefuse options\fP" +This parameter allows you to +specify a space\-separated list of rsync command line options that will +be refused by your rsync daemon. +You may specify the full option name, its one\-letter abbreviation, or a +wild\-card string that matches multiple options. +For example, this would refuse \fB\-\-checksum\fP (\fB\-c\fP) and all the various +delete options: +.IP +.RS +\f(CW refuse options = c delete\fP +.RE + +.IP +The reason the above refuses all delete options is that the options imply +\fB\-\-delete\fP, and implied options are refused just like explicit options. +As an additional safety feature, the refusal of \(dq\&delete\(dq\& also refuses +\fBremove\-source\-files\fP when the daemon is the sender; if you want the latter +without the former, instead refuse \(dq\&delete\-*\(dq\& \-\- that refuses all the +delete modes without affecting \fB\-\-remove\-source\-files\fP. +.IP +When an option is refused, the daemon prints an error message and exits. +To prevent all compression when serving files, +you can use \(dq\&dont compress = *\(dq\& (see below) +instead of \(dq\&refuse options = compress\(dq\& to avoid returning an error to a +client that requests compression. +.IP +.IP "\fBdont compress\fP" +This parameter allows you to select +filenames based on wildcard patterns that should not be compressed +when pulling files from the daemon (no analogous parameter exists to +govern the pushing of files to a daemon). +Compression is expensive in terms of CPU usage, so it +is usually good to not try to compress files that won\(cq\&t compress well, +such as already compressed files. +.IP +The \(dq\&dont compress\(dq\& parameter takes a space\-separated list of +case\-insensitive wildcard patterns. Any source filename matching one +of the patterns will not be compressed during transfer. +.IP +See the \fB\-\-skip\-compress\fP parameter in the \fBrsync\fP(1) manpage for the list +of file suffixes that are not compressed by default. Specifying a value +for the \(dq\&dont compress\(dq\& parameter changes the default when the daemon is +the sender. +.IP +.IP "\fBpre\-xfer exec\fP, \fBpost\-xfer exec\fP" +You may specify a command to be run +before and/or after the transfer. If the \fBpre\-xfer exec\fP command fails, the +transfer is aborted before it begins. Any output from the script on stdout (up +to several KB) will be displayed to the user when aborting, but is NOT +displayed if the script returns success. Any output from the script on stderr +goes to the daemon\(cq\&s stderr, which is typically discarded (though see +\-\-no\-detatch option for a way to see the stderr output, which can assist with +debugging). +.IP +The following environment variables will be set, though some are +specific to the pre\-xfer or the post\-xfer environment: +.IP +.RS +.IP o +\fBRSYNC_MODULE_NAME\fP: The name of the module being accessed. +.IP o +\fBRSYNC_MODULE_PATH\fP: The path configured for the module. +.IP o +\fBRSYNC_HOST_ADDR\fP: The accessing host\(cq\&s IP address. +.IP o +\fBRSYNC_HOST_NAME\fP: The accessing host\(cq\&s name. +.IP o +\fBRSYNC_USER_NAME\fP: The accessing user\(cq\&s name (empty if no user). +.IP o +\fBRSYNC_PID\fP: A unique number for this transfer. +.IP o +\fBRSYNC_REQUEST\fP: (pre\-xfer only) The module/path info specified +by the user. Note that the user can specify multiple source files, +so the request can be something like \(dq\&mod/path1 mod/path2\(dq\&, etc. +.IP o +\fBRSYNC_ARG#\fP: (pre\-xfer only) The pre\-request arguments are set +in these numbered values. RSYNC_ARG0 is always \(dq\&rsyncd\(dq\&, followed by +the options that were used in RSYNC_ARG1, and so on. There will be a +value of \(dq\&.\(dq\& indicating that the options are done and the path args +are beginning \-\- these contain similar information to RSYNC_REQUEST, +but with values separated and the module name stripped off. +.IP o +\fBRSYNC_EXIT_STATUS\fP: (post\-xfer only) the server side\(cq\&s exit value. +This will be 0 for a successful run, a positive value for an error that the +server generated, or a \-1 if rsync failed to exit properly. Note that an +error that occurs on the client side does not currently get sent to the +server side, so this is not the final exit status for the whole transfer. +.IP o +\fBRSYNC_RAW_STATUS\fP: (post\-xfer only) the raw exit value from +\f(CWwaitpid()\fP +\&. +.RE + +.IP +Even though the commands can be associated with a particular module, they +are run using the permissions of the user that started the daemon (not the +module\(cq\&s uid/gid setting) without any chroot restrictions. +.IP +.SH "CONFIG DIRECTIVES" + +.PP +There are currently two config directives available that allow a config file to +incorporate the contents of other files: \fB&include\fP and \fB&merge\fP. Both +allow a reference to either a file or a directory. They differ in how +segregated the file\(cq\&s contents are considered to be. +.PP +The \fB&include\fP directive treats each file as more distinct, with each one +inheriting the defaults of the parent file, starting the parameter parsing +as globals/defaults, and leaving the defaults unchanged for the parsing of +the rest of the parent file. +.PP +The \fB&merge\fP directive, on the other hand, treats the file\(cq\&s contents as +if it were simply inserted in place of the directive, and thus it can set +parameters in a module started in another file, can affect the defaults for +other files, etc. +.PP +When an \fB&include\fP or \fB&merge\fP directive refers to a directory, it will read +in all the \fB*.conf\fP or \fB*.inc\fP files (respectively) that are contained inside +that directory (without any +recursive scanning), with the files sorted into alpha order. So, if you have a +directory named \(dq\&rsyncd.d\(dq\& with the files \(dq\&foo.conf\(dq\&, \(dq\&bar.conf\(dq\&, and +\(dq\&baz.conf\(dq\& inside it, this directive: +.PP +.nf + &include /path/rsyncd.d +.fi + +.PP +would be the same as this set of directives: +.PP +.nf + &include /path/rsyncd.d/bar.conf + &include /path/rsyncd.d/baz.conf + &include /path/rsyncd.d/foo.conf +.fi + +.PP +except that it adjusts as files are added and removed from the directory. +.PP +The advantage of the \fB&include\fP directive is that you can define one or more +modules in a separate file without worrying about unintended side\-effects +between the self\-contained module files. +.PP +The advantage of the \fB&merge\fP directive is that you can load config snippets +that can be included into multiple module definitions, and you can also set +global values that will affect connections (such as \fBmotd file\fP), or globals +that will affect other include files. +.PP +For example, this is a useful /etc/rsyncd.conf file: +.PP +.nf + port = 873 + log file = /var/log/rsync.log + pid file = /var/lock/rsync.lock + + &merge /etc/rsyncd.d + &include /etc/rsyncd.d +.fi + +.PP +This would merge any /etc/rsyncd.d/*.inc files (for global values that should +stay in effect), and then include any /etc/rsyncd.d/*.conf files (defining +modules without any global\-value cross\-talk). +.PP +.SH "AUTHENTICATION STRENGTH" + +.PP +The authentication protocol used in rsync is a 128 bit MD4 based +challenge response system. This is fairly weak protection, though (with +at least one brute\-force hash\-finding algorithm publicly available), so +if you want really top\-quality security, then I recommend that you run +rsync over ssh. (Yes, a future version of rsync will switch over to a +stronger hashing method.) +.PP +Also note that the rsync daemon protocol does not currently provide any +encryption of the data that is transferred over the connection. Only +authentication is provided. Use ssh as the transport if you want +encryption. +.PP +Future versions of rsync may support SSL for better authentication and +encryption, but that is still being investigated. +.PP +.SH "EXAMPLES" + +.PP +A simple rsyncd.conf file that allow anonymous rsync to a ftp area at +\f(CW/home/ftp\fP would be: +.PP +.nf + +[ftp] + path = /home/ftp + comment = ftp export area + +.fi + +.PP +A more sophisticated example would be: +.PP +.nf + +uid = nobody +gid = nobody +use chroot = yes +max connections = 4 +syslog facility = local5 +pid file = /var/run/rsyncd.pid + +[ftp] + path = /var/ftp/./pub + comment = whole ftp area (approx 6.1 GB) + +[sambaftp] + path = /var/ftp/./pub/samba + comment = Samba ftp area (approx 300 MB) + +[rsyncftp] + path = /var/ftp/./pub/rsync + comment = rsync ftp area (approx 6 MB) + +[sambawww] + path = /public_html/samba + comment = Samba WWW pages (approx 240 MB) + +[cvs] + path = /data/cvs + comment = CVS repository (requires authentication) + auth users = tridge, susan + secrets file = /etc/rsyncd.secrets + +.fi + +.PP +The /etc/rsyncd.secrets file would look something like this: +.PP +.RS +\f(CWtridge:mypass\fP +.br +\f(CWsusan:herpass\fP +.br +.RE + +.PP +.SH "FILES" + +.PP +/etc/rsyncd.conf or rsyncd.conf +.PP +.SH "SEE ALSO" + +.PP +\fBrsync\fP(1) +.PP +.SH "DIAGNOSTICS" + +.PP +.SH "BUGS" + +.PP +Please report bugs! The rsync bug tracking system is online at +http://rsync.samba.org/ +.PP +.SH "VERSION" + +.PP +This man page is current for version 3.1.1 of rsync. +.PP +.SH "CREDITS" + +.PP +rsync is distributed under the GNU General Public License. See the file +COPYING for details. +.PP +The primary ftp site for rsync is +ftp://rsync.samba.org/pub/rsync. +.PP +A WEB site is available at +http://rsync.samba.org/ +.PP +We would be delighted to hear from you if you like this program. +.PP +This program uses the zlib compression library written by Jean\-loup +Gailly and Mark Adler. +.PP +.SH "THANKS" + +.PP +Thanks to Warren Stanley for his original idea and patch for the rsync +daemon. Thanks to Karsten Thygesen for his many suggestions and +documentation! +.PP +.SH "AUTHOR" + +.PP +rsync was written by Andrew Tridgell and Paul Mackerras. +Many people have later contributed to it. +.PP +Mailing lists for support and development are available at +http://lists.samba.org diff --git a/rsync/rsyncd.conf.yo b/rsync/rsyncd.conf.yo new file mode 100644 index 0000000..9f9f6ac --- /dev/null +++ b/rsync/rsyncd.conf.yo @@ -0,0 +1,934 @@ +mailto(rsync-bugs@samba.org) +manpage(rsyncd.conf)(5)(22 Jun 2014)()() +manpagename(rsyncd.conf)(configuration file for rsync in daemon mode) +manpagesynopsis() + +rsyncd.conf + +manpagedescription() + +The rsyncd.conf file is the runtime configuration file for rsync when +run as an rsync daemon. + +The rsyncd.conf file controls authentication, access, logging and +available modules. + +manpagesection(FILE FORMAT) + +The file consists of modules and parameters. A module begins with the +name of the module in square brackets and continues until the next +module begins. Modules contain parameters of the form "name = value". + +The file is line-based -- that is, each newline-terminated line represents +either a comment, a module name or a parameter. + +Only the first equals sign in a parameter is significant. Whitespace before +or after the first equals sign is discarded. Leading, trailing and internal +whitespace in module and parameter names is irrelevant. Leading and +trailing whitespace in a parameter value is discarded. Internal whitespace +within a parameter value is retained verbatim. + +Any line bf(beginning) with a hash (#) is ignored, as are lines containing +only whitespace. (If a hash occurs after anything other than leading +whitespace, it is considered a part of the line's content.) + +Any line ending in a \ is "continued" on the next line in the +customary UNIX fashion. + +The values following the equals sign in parameters are all either a string +(no quotes needed) or a boolean, which may be given as yes/no, 0/1 or +true/false. Case is not significant in boolean values, but is preserved +in string values. + +manpagesection(LAUNCHING THE RSYNC DAEMON) + +The rsync daemon is launched by specifying the bf(--daemon) option to +rsync. + +The daemon must run with root privileges if you wish to use chroot, to +bind to a port numbered under 1024 (as is the default 873), or to set +file ownership. Otherwise, it must just have permission to read and +write the appropriate data, log, and lock files. + +You can launch it either via inetd, as a stand-alone daemon, or from +an rsync client via a remote shell. If run as a stand-alone daemon then +just run the command "bf(rsync --daemon)" from a suitable startup script. + +When run via inetd you should add a line like this to /etc/services: + +verb( rsync 873/tcp) + +and a single line something like this to /etc/inetd.conf: + +verb( rsync stream tcp nowait root /usr/bin/rsync rsyncd --daemon) + +Replace "/usr/bin/rsync" with the path to where you have rsync installed on +your system. You will then need to send inetd a HUP signal to tell it to +reread its config file. + +Note that you should bf(not) send the rsync daemon a HUP signal to force +it to reread the tt(rsyncd.conf) file. The file is re-read on each client +connection. + +manpagesection(GLOBAL PARAMETERS) + +The first parameters in the file (before a [module] header) are the +global parameters. + +You may also include any module parameters in the global part of the +config file in which case the supplied value will override the +default for that parameter. + +You may use references to environment variables in the values of parameters. +String parameters will have %VAR% references expanded as late as possible (when +the string is used in the program), allowing for the use of variables that +rsync sets at connection time, such as RSYNC_USER_NAME. Non-string parameters +(such as true/false settings) are expanded when read from the config file. If +a variable does not exist in the environment, or if a sequence of characters is +not a valid reference (such as an un-paired percent sign), the raw characters +are passed through unchanged. This helps with backward compatibility and +safety (e.g. expanding a non-existent %VAR% to an empty string in a path could +result in a very unsafe path). The safest way to insert a literal % into a +value is to use %%. + +startdit() +dit(bf(motd file)) This parameter allows you to specify a +"message of the day" to display to clients on each connect. This +usually contains site information and any legal notices. The default +is no motd file. +This can be overridden by the bf(--dparam=motdfile=FILE) +command-line option when starting the daemon. + +dit(bf(pid file)) This parameter tells the rsync daemon to write +its process ID to that file. If the file already exists, the rsync +daemon will abort rather than overwrite the file. +This can be overridden by the bf(--dparam=pidfile=FILE) +command-line option when starting the daemon. + +dit(bf(port)) You can override the default port the daemon will listen on +by specifying this value (defaults to 873). This is ignored if the daemon +is being run by inetd, and is superseded by the bf(--port) command-line option. + +dit(bf(address)) You can override the default IP address the daemon +will listen on by specifying this value. This is ignored if the daemon is +being run by inetd, and is superseded by the bf(--address) command-line option. + +dit(bf(socket options)) This parameter can provide endless fun for people +who like to tune their systems to the utmost degree. You can set all +sorts of socket options which may make transfers faster (or +slower!). Read the man page for the code(setsockopt()) system call for +details on some of the options you may be able to set. By default no +special socket options are set. These settings can also be specified +via the bf(--sockopts) command-line option. + +dit(bf(listen backlog)) You can override the default backlog value when the +daemon listens for connections. It defaults to 5. + +enddit() + +manpagesection(MODULE PARAMETERS) + +After the global parameters you should define a number of modules, each +module exports a directory tree as a symbolic name. Modules are +exported by specifying a module name in square brackets [module] +followed by the parameters for that module. +The module name cannot contain a slash or a closing square bracket. If the +name contains whitespace, each internal sequence of whitespace will be +changed into a single space, while leading or trailing whitespace will be +discarded. + +As with GLOBAL PARAMETERS, you may use references to environment variables in +the values of parameters. See the GLOBAL PARAMETERS section for more details. + +startdit() + +dit(bf(comment)) This parameter specifies a description string +that is displayed next to the module name when clients obtain a list +of available modules. The default is no comment. + +dit(bf(path)) This parameter specifies the directory in the daemon's +filesystem to make available in this module. You must specify this parameter +for each module in tt(rsyncd.conf). + +You may base the path's value off of an environment variable by surrounding +the variable name with percent signs. You can even reference a variable +that is set by rsync when the user connects. +For example, this would use the authorizing user's name in the path: + +verb( path = /home/%RSYNC_USER_NAME% ) + +It is fine if the path includes internal spaces -- they will be retained +verbatim (which means that you shouldn't try to escape them). If your final +directory has a trailing space (and this is somehow not something you wish to +fix), append a trailing slash to the path to avoid losing the trailing +whitespace. + +dit(bf(use chroot)) If "use chroot" is true, the rsync daemon will chroot +to the "path" before starting the file transfer with the client. This has +the advantage of extra protection against possible implementation security +holes, but it has the disadvantages of requiring super-user privileges, +of not being able to follow symbolic links that are either absolute or outside +of the new root path, and of complicating the preservation of users and groups +by name (see below). + +As an additional safety feature, you can specify a dot-dir in the module's +"path" to indicate the point where the chroot should occur. This allows rsync +to run in a chroot with a non-"/" path for the top of the transfer hierarchy. +Doing this guards against unintended library loading (since those absolute +paths will not be inside the transfer hierarchy unless you have used an unwise +pathname), and lets you setup libraries for the chroot that are outside of the +transfer. For example, specifying "/var/rsync/./module1" will chroot to the +"/var/rsync" directory and set the inside-chroot path to "/module1". If you +had omitted the dot-dir, the chroot would have used the whole path, and the +inside-chroot path would have been "/". + +When "use chroot" is false or the inside-chroot path is not "/", rsync will: +(1) munge symlinks by +default for security reasons (see "munge symlinks" for a way to turn this +off, but only if you trust your users), (2) substitute leading slashes in +absolute paths with the module's path (so that options such as +bf(--backup-dir), bf(--compare-dest), etc. interpret an absolute path as +rooted in the module's "path" dir), and (3) trim ".." path elements from +args if rsync believes they would escape the module hierarchy. +The default for "use chroot" is true, and is the safer choice (especially +if the module is not read-only). + +When this parameter is enabled, rsync will not attempt to map users and groups +by name (by default), but instead copy IDs as though bf(--numeric-ids) had +been specified. In order to enable name-mapping, rsync needs to be able to +use the standard library functions for looking up names and IDs (i.e. +code(getpwuid()), code(getgrgid()), code(getpwname()), and code(getgrnam())). +This means the rsync +process in the chroot hierarchy will need to have access to the resources +used by these library functions (traditionally /etc/passwd and +/etc/group, but perhaps additional dynamic libraries as well). + +If you copy the necessary resources into the module's chroot area, you +should protect them through your OS's normal user/group or ACL settings (to +prevent the rsync module's user from being able to change them), and then +hide them from the user's view via "exclude" (see how in the discussion of +that parameter). At that point it will be safe to enable the mapping of users +and groups by name using the "numeric ids" daemon parameter (see below). + +Note also that you are free to setup custom user/group information in the +chroot area that is different from your normal system. For example, you +could abbreviate the list of users and groups. + +dit(bf(numeric ids)) Enabling this parameter disables the mapping +of users and groups by name for the current daemon module. This prevents +the daemon from trying to load any user/group-related files or libraries. +This enabling makes the transfer behave as if the client had passed +the bf(--numeric-ids) command-line option. By default, this parameter is +enabled for chroot modules and disabled for non-chroot modules. + +A chroot-enabled module should not have this parameter enabled unless you've +taken steps to ensure that the module has the necessary resources it needs +to translate names, and that it is not possible for a user to change those +resources. + +dit(bf(munge symlinks)) This parameter tells rsync to modify +all symlinks in the same way as the (non-daemon-affecting) +bf(--munge-links) command-line option (using a method described below). +This should help protect your files from user trickery when +your daemon module is writable. The default is disabled when "use chroot" +is on and the inside-chroot path is "/", otherwise it is enabled. + +If you disable this parameter on a daemon that is not read-only, there +are tricks that a user can play with uploaded symlinks to access +daemon-excluded items (if your module has any), and, if "use chroot" +is off, rsync can even be tricked into showing or changing data that +is outside the module's path (as access-permissions allow). + +The way rsync disables the use of symlinks is to prefix each one with +the string "/rsyncd-munged/". This prevents the links from being used +as long as that directory does not exist. When this parameter is enabled, +rsync will refuse to run if that path is a directory or a symlink to +a directory. When using the "munge symlinks" parameter in a chroot area +that has an inside-chroot path of "/", you should add "/rsyncd-munged/" +to the exclude setting for the module so that +a user can't try to create it. + +Note: rsync makes no attempt to verify that any pre-existing symlinks in +the module's hierarchy are as safe as you want them to be (unless, of +course, it just copied in the whole hierarchy). If you setup an rsync +daemon on a new area or locally add symlinks, you can manually protect your +symlinks from being abused by prefixing "/rsyncd-munged/" to the start of +every symlink's value. There is a perl script in the support directory +of the source code named "munge-symlinks" that can be used to add or remove +this prefix from your symlinks. + +When this parameter is disabled on a writable module and "use chroot" is off +(or the inside-chroot path is not "/"), +incoming symlinks will be modified to drop a leading slash and to remove ".." +path elements that rsync believes will allow a symlink to escape the module's +hierarchy. There are tricky ways to work around this, though, so you had +better trust your users if you choose this combination of parameters. + +dit(bf(charset)) This specifies the name of the character set in which the +module's filenames are stored. If the client uses an bf(--iconv) option, +the daemon will use the value of the "charset" parameter regardless of the +character set the client actually passed. This allows the daemon to +support charset conversion in a chroot module without extra files in the +chroot area, and also ensures that name-translation is done in a consistent +manner. If the "charset" parameter is not set, the bf(--iconv) option is +refused, just as if "iconv" had been specified via "refuse options". + +If you wish to force users to always use bf(--iconv) for a particular +module, add "no-iconv" to the "refuse options" parameter. Keep in mind +that this will restrict access to your module to very new rsync clients. + +dit(bf(max connections)) This parameter allows you to +specify the maximum number of simultaneous connections you will allow. +Any clients connecting when the maximum has been reached will receive a +message telling them to try later. The default is 0, which means no limit. +A negative value disables the module. +See also the "lock file" parameter. + +dit(bf(log file)) When the "log file" parameter is set to a non-empty +string, the rsync daemon will log messages to the indicated file rather +than using syslog. This is particularly useful on systems (such as AIX) +where code(syslog()) doesn't work for chrooted programs. The file is +opened before code(chroot()) is called, allowing it to be placed outside +the transfer. If this value is set on a per-module basis instead of +globally, the global log will still contain any authorization failures +or config-file error messages. + +If the daemon fails to open the specified file, it will fall back to +using syslog and output an error about the failure. (Note that the +failure to open the specified log file used to be a fatal error.) + +This setting can be overridden by using the bf(--log-file=FILE) or +bf(--dparam=logfile=FILE) command-line options. The former overrides +all the log-file parameters of the daemon and all module settings. +The latter sets the daemon's log file and the default for all the +modules, which still allows modules to override the default setting. + +dit(bf(syslog facility)) This parameter allows you to +specify the syslog facility name to use when logging messages from the +rsync daemon. You may use any standard syslog facility name which is +defined on your system. Common names are auth, authpriv, cron, daemon, +ftp, kern, lpr, mail, news, security, syslog, user, uucp, local0, +local1, local2, local3, local4, local5, local6 and local7. The default +is daemon. This setting has no effect if the "log file" setting is a +non-empty string (either set in the per-modules settings, or inherited +from the global settings). + +dit(bf(max verbosity)) This parameter allows you to control +the maximum amount of verbose information that you'll allow the daemon to +generate (since the information goes into the log file). The default is 1, +which allows the client to request one level of verbosity. + +This also affects the user's ability to request higher levels of bf(--info) and +bf(--debug) logging. If the max value is 2, then no info and/or debug value +that is higher than what would be set by bf(-vv) will be honored by the daemon +in its logging. To see how high of a verbosity level you need to accept for a +particular info/debug level, refer to "rsync --info=help" and "rsync --debug=help". +For instance, it takes max-verbosity 4 to be able to output debug TIME2 and FLIST3. + +dit(bf(lock file)) This parameter specifies the file to use to +support the "max connections" parameter. The rsync daemon uses record +locking on this file to ensure that the max connections limit is not +exceeded for the modules sharing the lock file. +The default is tt(/var/run/rsyncd.lock). + +dit(bf(read only)) This parameter determines whether clients +will be able to upload files or not. If "read only" is true then any +attempted uploads will fail. If "read only" is false then uploads will +be possible if file permissions on the daemon side allow them. The default +is for all modules to be read only. + +Note that "auth users" can override this setting on a per-user basis. + +dit(bf(write only)) This parameter determines whether clients +will be able to download files or not. If "write only" is true then any +attempted downloads will fail. If "write only" is false then downloads +will be possible if file permissions on the daemon side allow them. The +default is for this parameter to be disabled. + +dit(bf(list)) This parameter determines whether this module is +listed when the client asks for a listing of available modules. In addition, +if this is false, the daemon will pretend the module does not exist +when a client denied by "hosts allow" or "hosts deny" attempts to access it. +Realize that if "reverse lookup" is disabled globally but enabled for the +module, the resulting reverse lookup to a potentially client-controlled DNS +server may still reveal to the client that it hit an existing module. +The default is for modules to be listable. + +dit(bf(uid)) This parameter specifies the user name or user ID that +file transfers to and from that module should take place as when the daemon +was run as root. In combination with the "gid" parameter this determines what +file permissions are available. The default when run by a super-user is to +switch to the system's "nobody" user. The default for a non-super-user is to +not try to change the user. See also the "gid" parameter. + +The RSYNC_USER_NAME environment variable may be used to request that rsync run +as the authorizing user. For example, if you want a rsync to run as the same +user that was received for the rsync authentication, this setup is useful: + +verb( uid = %RSYNC_USER_NAME% + gid = * ) + +dit(bf(gid)) This parameter specifies one or more group names/IDs that will be +used when accessing the module. The first one will be the default group, and +any extra ones be set as supplemental groups. You may also specify a "*" as +the first gid in the list, which will be replaced by all the normal groups for +the transfer's user (see "uid"). The default when run by a super-user is to +switch to your OS's "nobody" (or perhaps "nogroup") group with no other +supplementary groups. The default for a non-super-user is to not change any +group attributes (and indeed, your OS may not allow a non-super-user to try to +change their group settings). + +dit(bf(fake super)) Setting "fake super = yes" for a module causes the +daemon side to behave as if the bf(--fake-super) command-line option had +been specified. This allows the full attributes of a file to be stored +without having to have the daemon actually running as root. + +dit(bf(filter)) The daemon has its own filter chain that determines what files +it will let the client access. This chain is not sent to the client and is +independent of any filters the client may have specified. Files excluded by +the daemon filter chain (bf(daemon-excluded) files) are treated as non-existent +if the client tries to pull them, are skipped with an error message if the +client tries to push them (triggering exit code 23), and are never deleted from +the module. You can use daemon filters to prevent clients from downloading or +tampering with private administrative files, such as files you may add to +support uid/gid name translations. + +The daemon filter chain is built from the "filter", "include from", "include", +"exclude from", and "exclude" parameters, in that order of priority. Anchored +patterns are anchored at the root of the module. To prevent access to an +entire subtree, for example, "/secret", you em(must) exclude everything in the +subtree; the easiest way to do this is with a triple-star pattern like +"/secret/***". + +The "filter" parameter takes a space-separated list of daemon filter rules, +though it is smart enough to know not to split a token at an internal space in +a rule (e.g. "- /foo - /bar" is parsed as two rules). You may specify one or +more merge-file rules using the normal syntax. Only one "filter" parameter can +apply to a given module in the config file, so put all the rules you want in a +single parameter. Note that per-directory merge-file rules do not provide as +much protection as global rules, but they can be used to make bf(--delete) work +better during a client download operation if the per-dir merge files are +included in the transfer and the client requests that they be used. + +dit(bf(exclude)) This parameter takes a space-separated list of daemon +exclude patterns. As with the client bf(--exclude) option, patterns can be +qualified with "- " or "+ " to explicitly indicate exclude/include. Only one +"exclude" parameter can apply to a given module. See the "filter" parameter +for a description of how excluded files affect the daemon. + +dit(bf(include)) Use an "include" to override the effects of the "exclude" +parameter. Only one "include" parameter can apply to a given module. See the +"filter" parameter for a description of how excluded files affect the daemon. + +dit(bf(exclude from)) This parameter specifies the name of a file +on the daemon that contains daemon exclude patterns, one per line. Only one +"exclude from" parameter can apply to a given module; if you have multiple +exclude-from files, you can specify them as a merge file in the "filter" +parameter. See the "filter" parameter for a description of how excluded files +affect the daemon. + +dit(bf(include from)) Analogue of "exclude from" for a file of daemon include +patterns. Only one "include from" parameter can apply to a given module. See +the "filter" parameter for a description of how excluded files affect the +daemon. + +dit(bf(incoming chmod)) This parameter allows you to specify a set of +comma-separated chmod strings that will affect the permissions of all +incoming files (files that are being received by the daemon). These +changes happen after all other permission calculations, and this will +even override destination-default and/or existing permissions when the +client does not specify bf(--perms). +See the description of the bf(--chmod) rsync option and the bf(chmod)(1) +manpage for information on the format of this string. + +dit(bf(outgoing chmod)) This parameter allows you to specify a set of +comma-separated chmod strings that will affect the permissions of all +outgoing files (files that are being sent out from the daemon). These +changes happen first, making the sent permissions appear to be different +than those stored in the filesystem itself. For instance, you could +disable group write permissions on the server while having it appear to +be on to the clients. +See the description of the bf(--chmod) rsync option and the bf(chmod)(1) +manpage for information on the format of this string. + +dit(bf(auth users)) This parameter specifies a comma and/or space-separated +list of authorization rules. In its simplest form, you list the usernames +that will be allowed to connect to +this module. The usernames do not need to exist on the local +system. The rules may contain shell wildcard characters that will be matched +against the username provided by the client for authentication. If +"auth users" is set then the client will be challenged to supply a +username and password to connect to the module. A challenge response +authentication protocol is used for this exchange. The plain text +usernames and passwords are stored in the file specified by the +"secrets file" parameter. The default is for all users to be able to +connect without a password (this is called "anonymous rsync"). + +In addition to username matching, you can specify groupname matching via a '@' +prefix. When using groupname matching, the authenticating username must be a +real user on the system, or it will be assumed to be a member of no groups. +For example, specifying "@rsync" will match the authenticating user if the +named user is a member of the rsync group. + +Finally, options may be specified after a colon (:). The options allow you to +"deny" a user or a group, set the access to "ro" (read-only), or set the access +to "rw" (read/write). Setting an auth-rule-specific ro/rw setting overrides +the module's "read only" setting. + +Be sure to put the rules in the order you want them to be matched, because the +checking stops at the first matching user or group, and that is the only auth +that is checked. For example: + +verb( auth users = joe:deny @guest:deny admin:rw @rsync:ro susan joe sam ) + +In the above rule, user joe will be denied access no matter what. Any user +that is in the group "guest" is also denied access. The user "admin" gets +access in read/write mode, but only if the admin user is not in group "guest" +(because the admin user-matching rule would never be reached if the user is in +group "guest"). Any other user who is in group "rsync" will get read-only +access. Finally, users susan, joe, and sam get the ro/rw setting of the +module, but only if the user didn't match an earlier group-matching rule. + +See the description of the secrets file for how you can have per-user passwords +as well as per-group passwords. It also explains how a user can authenticate +using their user password or (when applicable) a group password, depending on +what rule is being authenticated. + +See also the section entitled "USING RSYNC-DAEMON FEATURES VIA A REMOTE +SHELL CONNECTION" in bf(rsync)(1) for information on how handle an +rsyncd.conf-level username that differs from the remote-shell-level +username when using a remote shell to connect to an rsync daemon. + +dit(bf(secrets file)) This parameter specifies the name of a file that contains +the username:password and/or @groupname:password pairs used for authenticating +this module. This file is only consulted if the "auth users" parameter is +specified. The file is line-based and contains one name:password pair per +line. Any line has a hash (#) as the very first character on the line is +considered a comment and is skipped. The passwords can contain any characters +but be warned that many operating systems limit the length of passwords that +can be typed at the client end, so you may find that passwords longer than 8 +characters don't work. + +The use of group-specific lines are only relevant when the module is being +authorized using a matching "@groupname" rule. When that happens, the user +can be authorized via either their "username:password" line or the +"@groupname:password" line for the group that triggered the authentication. + +It is up to you what kind of password entries you want to include, either +users, groups, or both. The use of group rules in "auth users" does not +require that you specify a group password if you do not want to use shared +passwords. + +There is no default for the "secrets file" parameter, you must choose a name +(such as tt(/etc/rsyncd.secrets)). The file must normally not be readable +by "other"; see "strict modes". If the file is not found or is rejected, no +logins for a "user auth" module will be possible. + +dit(bf(strict modes)) This parameter determines whether or not +the permissions on the secrets file will be checked. If "strict modes" is +true, then the secrets file must not be readable by any user ID other +than the one that the rsync daemon is running under. If "strict modes" is +false, the check is not performed. The default is true. This parameter +was added to accommodate rsync running on the Windows operating system. + +dit(bf(hosts allow)) This parameter allows you to specify a +list of patterns that are matched against a connecting clients +hostname and IP address. If none of the patterns match then the +connection is rejected. + +Each pattern can be in one of five forms: + +quote(itemization( + it() a dotted decimal IPv4 address of the form a.b.c.d, or an IPv6 address + of the form a:b:c::d:e:f. In this case the incoming machine's IP address + must match exactly. + it() an address/mask in the form ipaddr/n where ipaddr is the IP address + and n is the number of one bits in the netmask. All IP addresses which + match the masked IP address will be allowed in. + it() an address/mask in the form ipaddr/maskaddr where ipaddr is the + IP address and maskaddr is the netmask in dotted decimal notation for IPv4, + or similar for IPv6, e.g. ffff:ffff:ffff:ffff:: instead of /64. All IP + addresses which match the masked IP address will be allowed in. + it() a hostname pattern using wildcards. If the hostname of the connecting IP + (as determined by a reverse lookup) matches the wildcarded name (using the + same rules as normal unix filename matching), the client is allowed in. This + only works if "reverse lookup" is enabled (the default). + it() a hostname. A plain hostname is matched against the reverse DNS of the + connecting IP (if "reverse lookup" is enabled), and/or the IP of the given + hostname is matched against the connecting IP (if "forward lookup" is + enabled, as it is by default). Any match will be allowed in. +)) + +Note IPv6 link-local addresses can have a scope in the address specification: + +quote( +tt( fe80::1%link1)nl() +tt( fe80::%link1/64)nl() +tt( fe80::%link1/ffff:ffff:ffff:ffff::)nl() +) + +You can also combine "hosts allow" with a separate "hosts deny" +parameter. If both parameters are specified then the "hosts allow" parameter is +checked first and a match results in the client being able to +connect. The "hosts deny" parameter is then checked and a match means +that the host is rejected. If the host does not match either the +"hosts allow" or the "hosts deny" patterns then it is allowed to +connect. + +The default is no "hosts allow" parameter, which means all hosts can connect. + +dit(bf(hosts deny)) This parameter allows you to specify a +list of patterns that are matched against a connecting clients +hostname and IP address. If the pattern matches then the connection is +rejected. See the "hosts allow" parameter for more information. + +The default is no "hosts deny" parameter, which means all hosts can connect. + +dit(bf(reverse lookup)) Controls whether the daemon performs a reverse lookup +on the client's IP address to determine its hostname, which is used for +"hosts allow"/"hosts deny" checks and the "%h" log escape. This is enabled by +default, but you may wish to disable it to save time if you know the lookup will +not return a useful result, in which case the daemon will use the name +"UNDETERMINED" instead. + +If this parameter is enabled globally (even by default), rsync performs the +lookup as soon as a client connects, so disabling it for a module will not +avoid the lookup. Thus, you probably want to disable it globally and then +enable it for modules that need the information. + +dit(bf(forward lookup)) Controls whether the daemon performs a forward lookup +on any hostname specified in an hosts allow/deny setting. By default this is +enabled, allowing the use of an explicit hostname that would not be returned +by reverse DNS of the connecting IP. + +dit(bf(ignore errors)) This parameter tells rsyncd to +ignore I/O errors on the daemon when deciding whether to run the delete +phase of the transfer. Normally rsync skips the bf(--delete) step if any +I/O errors have occurred in order to prevent disastrous deletion due +to a temporary resource shortage or other I/O error. In some cases this +test is counter productive so you can use this parameter to turn off this +behavior. + +dit(bf(ignore nonreadable)) This tells the rsync daemon to completely +ignore files that are not readable by the user. This is useful for +public archives that may have some non-readable files among the +directories, and the sysadmin doesn't want those files to be seen at all. + +dit(bf(transfer logging)) This parameter enables per-file +logging of downloads and uploads in a format somewhat similar to that +used by ftp daemons. The daemon always logs the transfer at the end, so +if a transfer is aborted, no mention will be made in the log file. + +If you want to customize the log lines, see the "log format" parameter. + +dit(bf(log format)) This parameter allows you to specify the +format used for logging file transfers when transfer logging is enabled. +The format is a text string containing embedded single-character escape +sequences prefixed with a percent (%) character. An optional numeric +field width may also be specified between the percent and the escape +letter (e.g. "bf(%-50n %8l %07p)"). +In addition, one or more apostrophes may be specified prior to a numerical +escape to indicate that the numerical value should be made more human-readable. +The 3 supported levels are the same as for the bf(--human-readable) +command-line option, though the default is for human-readability to be off. +Each added apostrophe increases the level (e.g. "bf(%''l %'b %f)"). + +The default log format is "%o %h [%a] %m (%u) %f %l", and a "%t [%p] " +is always prefixed when using the "log file" parameter. +(A perl script that will summarize this default log format is included +in the rsync source code distribution in the "support" subdirectory: +rsyncstats.) + +The single-character escapes that are understood are as follows: + +quote(itemization( + it() %a the remote IP address (only available for a daemon) + it() %b the number of bytes actually transferred + it() %B the permission bits of the file (e.g. rwxrwxrwt) + it() %c the total size of the block checksums received for the basis file (only when sending) + it() %C the full-file MD5 checksum if bf(--checksum) is enabled or a file was transferred (only for protocol 30 or above). + it() %f the filename (long form on sender; no trailing "/") + it() %G the gid of the file (decimal) or "DEFAULT" + it() %h the remote host name (only available for a daemon) + it() %i an itemized list of what is being updated + it() %l the length of the file in bytes + it() %L the string " -> SYMLINK", " => HARDLINK", or "" (where bf(SYMLINK) or bf(HARDLINK) is a filename) + it() %m the module name + it() %M the last-modified time of the file + it() %n the filename (short form; trailing "/" on dir) + it() %o the operation, which is "send", "recv", or "del." (the latter includes the trailing period) + it() %p the process ID of this rsync session + it() %P the module path + it() %t the current date time + it() %u the authenticated username or an empty string + it() %U the uid of the file (decimal) +)) + +For a list of what the characters mean that are output by "%i", see the +bf(--itemize-changes) option in the rsync manpage. + +Note that some of the logged output changes when talking with older +rsync versions. For instance, deleted files were only output as verbose +messages prior to rsync 2.6.4. + +dit(bf(timeout)) This parameter allows you to override the +clients choice for I/O timeout for this module. Using this parameter you +can ensure that rsync won't wait on a dead client forever. The timeout +is specified in seconds. A value of zero means no timeout and is the +default. A good choice for anonymous rsync daemons may be 600 (giving +a 10 minute timeout). + +dit(bf(refuse options)) This parameter allows you to +specify a space-separated list of rsync command line options that will +be refused by your rsync daemon. +You may specify the full option name, its one-letter abbreviation, or a +wild-card string that matches multiple options. +For example, this would refuse bf(--checksum) (bf(-c)) and all the various +delete options: + +quote(tt( refuse options = c delete)) + +The reason the above refuses all delete options is that the options imply +bf(--delete), and implied options are refused just like explicit options. +As an additional safety feature, the refusal of "delete" also refuses +bf(remove-source-files) when the daemon is the sender; if you want the latter +without the former, instead refuse "delete-*" -- that refuses all the +delete modes without affecting bf(--remove-source-files). + +When an option is refused, the daemon prints an error message and exits. +To prevent all compression when serving files, +you can use "dont compress = *" (see below) +instead of "refuse options = compress" to avoid returning an error to a +client that requests compression. + +dit(bf(dont compress)) This parameter allows you to select +filenames based on wildcard patterns that should not be compressed +when pulling files from the daemon (no analogous parameter exists to +govern the pushing of files to a daemon). +Compression is expensive in terms of CPU usage, so it +is usually good to not try to compress files that won't compress well, +such as already compressed files. + +The "dont compress" parameter takes a space-separated list of +case-insensitive wildcard patterns. Any source filename matching one +of the patterns will not be compressed during transfer. + +See the bf(--skip-compress) parameter in the bf(rsync)(1) manpage for the list +of file suffixes that are not compressed by default. Specifying a value +for the "dont compress" parameter changes the default when the daemon is +the sender. + +dit(bf(pre-xfer exec), bf(post-xfer exec)) You may specify a command to be run +before and/or after the transfer. If the bf(pre-xfer exec) command fails, the +transfer is aborted before it begins. Any output from the script on stdout (up +to several KB) will be displayed to the user when aborting, but is NOT +displayed if the script returns success. Any output from the script on stderr +goes to the daemon's stderr, which is typically discarded (though see +--no-detatch option for a way to see the stderr output, which can assist with +debugging). + +The following environment variables will be set, though some are +specific to the pre-xfer or the post-xfer environment: + +quote(itemization( + it() bf(RSYNC_MODULE_NAME): The name of the module being accessed. + it() bf(RSYNC_MODULE_PATH): The path configured for the module. + it() bf(RSYNC_HOST_ADDR): The accessing host's IP address. + it() bf(RSYNC_HOST_NAME): The accessing host's name. + it() bf(RSYNC_USER_NAME): The accessing user's name (empty if no user). + it() bf(RSYNC_PID): A unique number for this transfer. + it() bf(RSYNC_REQUEST): (pre-xfer only) The module/path info specified + by the user. Note that the user can specify multiple source files, + so the request can be something like "mod/path1 mod/path2", etc. + it() bf(RSYNC_ARG#): (pre-xfer only) The pre-request arguments are set + in these numbered values. RSYNC_ARG0 is always "rsyncd", followed by + the options that were used in RSYNC_ARG1, and so on. There will be a + value of "." indicating that the options are done and the path args + are beginning -- these contain similar information to RSYNC_REQUEST, + but with values separated and the module name stripped off. + it() bf(RSYNC_EXIT_STATUS): (post-xfer only) the server side's exit value. + This will be 0 for a successful run, a positive value for an error that the + server generated, or a -1 if rsync failed to exit properly. Note that an + error that occurs on the client side does not currently get sent to the + server side, so this is not the final exit status for the whole transfer. + it() bf(RSYNC_RAW_STATUS): (post-xfer only) the raw exit value from code(waitpid()). +)) + +Even though the commands can be associated with a particular module, they +are run using the permissions of the user that started the daemon (not the +module's uid/gid setting) without any chroot restrictions. + +enddit() + +manpagesection(CONFIG DIRECTIVES) + +There are currently two config directives available that allow a config file to +incorporate the contents of other files: bf(&include) and bf(&merge). Both +allow a reference to either a file or a directory. They differ in how +segregated the file's contents are considered to be. + +The bf(&include) directive treats each file as more distinct, with each one +inheriting the defaults of the parent file, starting the parameter parsing +as globals/defaults, and leaving the defaults unchanged for the parsing of +the rest of the parent file. + +The bf(&merge) directive, on the other hand, treats the file's contents as +if it were simply inserted in place of the directive, and thus it can set +parameters in a module started in another file, can affect the defaults for +other files, etc. + +When an bf(&include) or bf(&merge) directive refers to a directory, it will read +in all the bf(*.conf) or bf(*.inc) files (respectively) that are contained inside +that directory (without any +recursive scanning), with the files sorted into alpha order. So, if you have a +directory named "rsyncd.d" with the files "foo.conf", "bar.conf", and +"baz.conf" inside it, this directive: + +verb( &include /path/rsyncd.d ) + +would be the same as this set of directives: + +verb( &include /path/rsyncd.d/bar.conf + &include /path/rsyncd.d/baz.conf + &include /path/rsyncd.d/foo.conf ) + +except that it adjusts as files are added and removed from the directory. + +The advantage of the bf(&include) directive is that you can define one or more +modules in a separate file without worrying about unintended side-effects +between the self-contained module files. + +The advantage of the bf(&merge) directive is that you can load config snippets +that can be included into multiple module definitions, and you can also set +global values that will affect connections (such as bf(motd file)), or globals +that will affect other include files. + +For example, this is a useful /etc/rsyncd.conf file: + +verb( port = 873 + log file = /var/log/rsync.log + pid file = /var/lock/rsync.lock + + &merge /etc/rsyncd.d + &include /etc/rsyncd.d ) + +This would merge any /etc/rsyncd.d/*.inc files (for global values that should +stay in effect), and then include any /etc/rsyncd.d/*.conf files (defining +modules without any global-value cross-talk). + +manpagesection(AUTHENTICATION STRENGTH) + +The authentication protocol used in rsync is a 128 bit MD4 based +challenge response system. This is fairly weak protection, though (with +at least one brute-force hash-finding algorithm publicly available), so +if you want really top-quality security, then I recommend that you run +rsync over ssh. (Yes, a future version of rsync will switch over to a +stronger hashing method.) + +Also note that the rsync daemon protocol does not currently provide any +encryption of the data that is transferred over the connection. Only +authentication is provided. Use ssh as the transport if you want +encryption. + +Future versions of rsync may support SSL for better authentication and +encryption, but that is still being investigated. + +manpagesection(EXAMPLES) + +A simple rsyncd.conf file that allow anonymous rsync to a ftp area at +tt(/home/ftp) would be: + +verb( +[ftp] + path = /home/ftp + comment = ftp export area +) + +A more sophisticated example would be: + +verb( +uid = nobody +gid = nobody +use chroot = yes +max connections = 4 +syslog facility = local5 +pid file = /var/run/rsyncd.pid + +[ftp] + path = /var/ftp/./pub + comment = whole ftp area (approx 6.1 GB) + +[sambaftp] + path = /var/ftp/./pub/samba + comment = Samba ftp area (approx 300 MB) + +[rsyncftp] + path = /var/ftp/./pub/rsync + comment = rsync ftp area (approx 6 MB) + +[sambawww] + path = /public_html/samba + comment = Samba WWW pages (approx 240 MB) + +[cvs] + path = /data/cvs + comment = CVS repository (requires authentication) + auth users = tridge, susan + secrets file = /etc/rsyncd.secrets +) + +The /etc/rsyncd.secrets file would look something like this: + +quote( +tt(tridge:mypass)nl() +tt(susan:herpass)nl() +) + +manpagefiles() + +/etc/rsyncd.conf or rsyncd.conf + +manpageseealso() + +bf(rsync)(1) + +manpagediagnostics() + +manpagebugs() + +Please report bugs! The rsync bug tracking system is online at +url(http://rsync.samba.org/)(http://rsync.samba.org/) + +manpagesection(VERSION) + +This man page is current for version 3.1.1 of rsync. + +manpagesection(CREDITS) + +rsync is distributed under the GNU General Public License. See the file +COPYING for details. + +The primary ftp site for rsync is +url(ftp://rsync.samba.org/pub/rsync)(ftp://rsync.samba.org/pub/rsync). + +A WEB site is available at +url(http://rsync.samba.org/)(http://rsync.samba.org/) + +We would be delighted to hear from you if you like this program. + +This program uses the zlib compression library written by Jean-loup +Gailly and Mark Adler. + +manpagesection(THANKS) + +Thanks to Warren Stanley for his original idea and patch for the rsync +daemon. Thanks to Karsten Thygesen for his many suggestions and +documentation! + +manpageauthor() + +rsync was written by Andrew Tridgell and Paul Mackerras. +Many people have later contributed to it. + +Mailing lists for support and development are available at +url(http://lists.samba.org)(lists.samba.org) diff --git a/rsync/rsyncsh.txt b/rsync/rsyncsh.txt new file mode 100644 index 0000000..93932dc --- /dev/null +++ b/rsync/rsyncsh.txt @@ -0,0 +1,26 @@ +rsyncsh +Copyright (C) 2001 by Martin Pool + +This is a quick hack to build an interactive shell around rsync, the +same way we have the ftp, lftp and ncftp programs for the FTP +protocol. The key application for this is connecting to a public +rsync server, such as rsync.kernel.org, change down through and list +directories, and finally pull down the file you want. + +rsync is somewhat ill-at-ease as an interactive operation, since every +network connection is used to carry out exactly one operation. rsync +kind of "forks across the network" passing the options and filenames +to operate upon, and the connection is closed when the transfer is +complete. (This might be fixed in the future, either by adapting the +current protocol to allow chained operations over a single socket, or +by writing a new protocol that better supports interactive use.) + +So, rsyncsh runs a new rsync command and opens a new socket for every +(network-based) command you type. + +This has two consequences. Firstly, there is more command latency +than is really desirable. More seriously, if the connection cannot be +done automatically, because for example it uses SSH with a password, +then you will need to enter the password every time. We might even +fix this in the future, though, by having a way to automatically feed +the password to SSH if it's entered once. diff --git a/rsync/runtests.sh b/rsync/runtests.sh new file mode 100755 index 0000000..42cd866 --- /dev/null +++ b/rsync/runtests.sh @@ -0,0 +1,334 @@ +#! /bin/sh + +# Copyright (C) 2001, 2002 by Martin Pool +# Copyright (C) 2003, 2004, 2005, 2006 Wayne Davison + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version +# 2 as published by the Free Software Foundation. +# +# 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +# rsync top-level test script -- this invokes all the other more +# detailed tests in order. This script can either be called by `make +# check' or `make installcheck'. `check' runs against the copies of +# the program and other files in the build directory, and +# `installcheck' against the installed copy of the program. + +# In either case we need to also be able to find the source directory, +# since we read test scripts and possibly other information from +# there. + +# Whenever possible, informational messages are written to stdout and +# error messages to stderr. They're separated out by the build farm +# display scripts. + +# According to the GNU autoconf manual, the only valid place to set up +# directory locations is through Make, since users are allowed to (try +# to) change their mind on the Make command line. So, Make has to +# pass in all the values we need. + +# For other configured settings we read ./config.sh, which tells us +# about shell commands on this machine and similar things. + +# rsync_bin gives the location of the rsync binary. This is either +# builddir/rsync if we're testing an uninstalled copy, or +# install_prefix/bin/rsync if we're testing an installed copy. On the +# build farm rsync will be installed, but into a scratch /usr. + +# srcdir gives the location of the source tree, which lets us find the +# build scripts. At the moment we assume we are invoked from the +# source directory. + +# This script must be invoked from the build directory. + +# A scratch directory, 'testtmp', is used in the build directory to +# hold per-test subdirectories. + +# This script also uses the $loglevel environment variable. 1 is the +# default value, and 10 the most verbose. You can set this from the +# Make command line. It's also set by the build farm to give more +# detail for failing builds. + + +# NOTES FOR TEST CASES: + +# Each test case runs in its own shell. + +# Exit codes from tests: + +# 1 tests failed +# 2 error in starting tests +# 77 this test skipped (random value unlikely to happen by chance, same as +# automake) + +# HOWEVER, the overall exit code to the farm is different: we return +# the *number of tests that failed*, so that it will show up nicely in +# the overall summary. + +# rsync.fns contains some general setup functions and definitions. + + +# NOTES ON PORTABILITY: + +# Both this script and the Makefile have to be pretty conservative +# about which Unix features they use. + +# We cannot count on Make exporting variables to commands, unless +# they're explicitly given on the command line. + +# Also, we can't count on 'cp -a' or 'mkdir -p', although they're +# pretty handy (see function makepath for the latter). + +# I think some of the GNU documentation suggests that we shouldn't +# rely on shell functions. However, the Bash manual seems to say that +# they're in POSIX 1003.2, and since the build farm relies on them +# they're probably working on most machines we really care about. + +# You cannot use "function foo {" syntax, but must instead say "foo() +# {", or it breaks on FreeBSD. + +# BSD machines tend not to have "head" or "seq". + +# You cannot do "export VAR=VALUE" all on one line; the export must be +# separate from the assignment. (SCO SysV) + +# Don't rely on grep -q, as that doesn't work everywhere -- just redirect +# stdout to /dev/null to keep it quiet. + + +# STILL TO DO: + +# We need a good protection against tests that hang indefinitely. +# Perhaps some combination of starting them in the background, wait, +# and kill? + +# Perhaps we need a common way to cleanup tests. At the moment just +# clobbering the directory when we're done should be enough. + +# If any of the targets fail, then (GNU?) Make returns 2, instead of +# the return code from the failing command. This is fine, but it +# means that the build farm just shows "2" for failed tests, not the +# number of tests that actually failed. For more details we might +# need to grovel through the log files to find a line saying how many +# failed. + + +set -e + +. "./shconfig" + +RUNSHFLAGS='-e' +export RUNSHFLAGS + +# for Solaris +if [ -d /usr/xpg4/bin ]; then + PATH="/usr/xpg4/bin/:$PATH" + export PATH +fi + +if [ "x$loglevel" != x ] && [ "$loglevel" -gt 8 ]; then + if set -x; then + # If it doesn't work the first time, don't keep trying. + RUNSHFLAGS="$RUNSHFLAGS -x" + fi +fi + +POSIXLY_CORRECT=1 +if test x"$TOOLDIR" = x; then + TOOLDIR=`pwd` +fi +srcdir=`dirname $0` +if test x"$srcdir" = x -o x"$srcdir" = x.; then + srcdir="$TOOLDIR" +fi +if test x"$rsync_bin" = x; then + rsync_bin="$TOOLDIR/rsync" +fi + +# This allows the user to specify extra rsync options -- use carefully! +RSYNC="$rsync_bin $*" +#RSYNC="valgrind $rsync_bin $*" + +TLS_ARGS='' +if egrep '^#define HAVE_LUTIMES 1' config.h >/dev/null; then + TLS_ARGS="$TLS_ARGS -l" +fi +if egrep '#undef CHOWN_MODIFIES_SYMLINK' config.h >/dev/null; then + TLS_ARGS="$TLS_ARGS -L" +fi + +export POSIXLY_CORRECT TOOLDIR srcdir RSYNC TLS_ARGS + +echo "============================================================" +echo "$0 running in $TOOLDIR" +echo " rsync_bin=$RSYNC" +echo " srcdir=$srcdir" +echo " TLS_ARGS=$TLS_ARGS" + +if [ -f /usr/bin/whoami ]; then + testuser=`/usr/bin/whoami` +elif [ -f /usr/ucb/whoami ]; then + testuser=`/usr/ucb/whoami` +elif [ -f /bin/whoami ]; then + testuser=`/bin/whoami` +else + testuser=`id -un 2>/dev/null || echo ${LOGNAME:-${USERNAME:-${USER:-'UNKNOWN'}}}` +fi + +echo " testuser=$testuser" +echo " os=`uname -a`" + +# It must be "yes", not just nonnull +if [ "x$preserve_scratch" = xyes ]; then + echo " preserve_scratch=yes" +else + echo " preserve_scratch=no" +fi + +# Check if setacl/setfacl is around and if it supports the -k or -s option. +if setacl -k u::7,g::5,o:5 testsuite 2>/dev/null; then + setfacl_nodef='setacl -k' +elif setfacl --help 2>&1 | grep ' -k,\|\[-[a-z]*k' >/dev/null; then + setfacl_nodef='setfacl -k' +elif setfacl -s u::7,g::5,o:5 testsuite 2>/dev/null; then + setfacl_nodef='setfacl -s u::7,g::5,o:5' +else + # The "true" command runs successfully, but does nothing. + setfacl_nodef=true +fi + +export setfacl_nodef + +if [ ! -f "$rsync_bin" ]; then + echo "rsync_bin $rsync_bin is not a file" >&2 + exit 2 +fi + +if [ ! -d "$srcdir" ]; then + echo "srcdir $srcdir is not a directory" >&2 + exit 2 +fi + +skipped=0 +missing=0 +passed=0 +failed=0 + +# Directory that holds the other test subdirs. We create separate dirs +# inside for each test case, so that they can be left behind in case of +# failure to aid investigation. We don't remove the testtmp subdir at +# the end so that it can be configured as a symlink to a filesystem that +# has ACLs and xattr support enabled (if desired). +scratchbase="$TOOLDIR"/testtmp +echo " scratchbase=$scratchbase" +[ -d "$scratchbase" ] || mkdir "$scratchbase" + +suitedir="$srcdir/testsuite" +TESTRUN_TIMEOUT=300 + +export scratchdir suitedir TESTRUN_TIMEOUT + +prep_scratch() { + [ -d "$scratchdir" ] && chmod -R u+rwX "$scratchdir" && rm -rf "$scratchdir" + mkdir "$scratchdir" + # Get rid of default ACLs and dir-setgid to avoid confusing some tests. + $setfacl_nodef "$scratchdir" || true + chmod g-s "$scratchdir" + case "$srcdir" in + /*) ln -s "$srcdir" "$scratchdir/src" ;; + *) ln -s "$TOOLDIR/$srcdir" "$scratchdir/src" ;; + esac + return 0 +} + +maybe_discard_scratch() { + [ x"$preserve_scratch" != xyes ] && [ -d "$scratchdir" ] && rm -rf "$scratchdir" + return 0 +} + +if [ "x$whichtests" = x ]; then + whichtests="*.test" +fi + +for testscript in $suitedir/$whichtests +do + testbase=`echo $testscript | sed -e 's!.*/!!' -e 's/.test\$//'` + scratchdir="$scratchbase/$testbase" + + prep_scratch + + case "$testscript" in + *hardlinks*) TESTRUN_TIMEOUT=600 ;; + *) TESTRUN_TIMEOUT=300 ;; + esac + + set +e + "$TOOLDIR/"testrun $RUNSHFLAGS "$testscript" >"$scratchdir/test.log" 2>&1 + result=$? + set -e + + if [ "x$always_log" = xyes -o \( $result != 0 -a $result != 77 -a $result != 78 \) ] + then + echo "----- $testbase log follows" + cat "$scratchdir/test.log" + echo "----- $testbase log ends" + if [ -f "$scratchdir/rsyncd.log" ]; then + echo "----- $testbase rsyncd.log follows" + cat "$scratchdir/rsyncd.log" + echo "----- $testbase rsyncd.log ends" + fi + fi + + case $result in + 0) + echo "PASS $testbase" + passed=`expr $passed + 1` + maybe_discard_scratch + ;; + 77) + # backticks will fill the whole file onto one line, which is a feature + whyskipped=`cat "$scratchdir/whyskipped"` + echo "SKIP $testbase ($whyskipped)" + skipped=`expr $skipped + 1` + maybe_discard_scratch + ;; + 78) + # It failed, but we expected that. don't dump out error logs, + # because most users won't want to see them. But do leave + # the working directory around. + echo "XFAIL $testbase" + failed=`expr $failed + 1` + ;; + *) + echo "FAIL $testbase" + failed=`expr $failed + 1` + if [ "x$nopersist" = xyes ]; then + exit 1 + fi + esac +done + +echo '------------------------------------------------------------' +echo "----- overall results:" +echo " $passed passed" +[ "$failed" -gt 0 ] && echo " $failed failed" +[ "$skipped" -gt 0 ] && echo " $skipped skipped" +[ "$missing" -gt 0 ] && echo " $missing missing" +echo '------------------------------------------------------------' + +# OK, so expr exits with 0 if the result is neither null nor zero; and +# 1 if the expression is null or zero. This is the opposite of what +# we want, and if we just call expr then this script will always fail, +# because -e is set. + +result=`expr $failed + $missing || true` +echo "overall result is $result" +exit $result diff --git a/rsync/sender.c b/rsync/sender.c new file mode 100644 index 0000000..5adc2fd --- /dev/null +++ b/rsync/sender.c @@ -0,0 +1,430 @@ +/* + * Routines only used by the sending process. + * + * Copyright (C) 1996 Andrew Tridgell + * Copyright (C) 1996 Paul Mackerras + * Copyright (C) 2003-2014 Wayne Davison + * + * 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, visit the http://fsf.org website. + */ + +#include "rsync.h" +#include "inums.h" + +extern int do_xfers; +extern int am_server; +extern int am_daemon; +extern int inc_recurse; +extern int log_before_transfer; +extern int stdout_format_has_i; +extern int logfile_format_has_i; +extern int want_xattr_optim; +extern int csum_length; +extern int append_mode; +extern int io_error; +extern int flist_eof; +extern int allowed_lull; +extern int preserve_xattrs; +extern int protocol_version; +extern int remove_source_files; +extern int updating_basis_file; +extern int make_backups; +extern int inplace; +extern int batch_fd; +extern int write_batch; +extern int file_old_total; +extern struct stats stats; +extern struct file_list *cur_flist, *first_flist, *dir_flist; + +BOOL extra_flist_sending_enabled; + +/** + * @file + * + * The sender gets checksums from the generator, calculates deltas, + * and transmits them to the receiver. The sender process runs on the + * machine holding the source files. + **/ + +/** + * Receive the checksums for a buffer + **/ +static struct sum_struct *receive_sums(int f) +{ + struct sum_struct *s; + int32 i; + int lull_mod = protocol_version >= 31 ? 0 : allowed_lull * 5; + OFF_T offset = 0; + + if (!(s = new(struct sum_struct))) + out_of_memory("receive_sums"); + + read_sum_head(f, s); + + s->sums = NULL; + + if (DEBUG_GTE(DELTASUM, 3)) { + rprintf(FINFO, "count=%s n=%ld rem=%ld\n", + big_num(s->count), (long)s->blength, (long)s->remainder); + } + + if (append_mode > 0) { + s->flength = (OFF_T)s->count * s->blength; + if (s->remainder) + s->flength -= s->blength - s->remainder; + return s; + } + + if (s->count == 0) + return(s); + + if (!(s->sums = new_array(struct sum_buf, s->count))) + out_of_memory("receive_sums"); + + for (i = 0; i < s->count; i++) { + s->sums[i].sum1 = read_int(f); + read_buf(f, s->sums[i].sum2, s->s2length); + + s->sums[i].offset = offset; + s->sums[i].flags = 0; + + if (i == s->count-1 && s->remainder != 0) + s->sums[i].len = s->remainder; + else + s->sums[i].len = s->blength; + offset += s->sums[i].len; + + if (lull_mod && !(i % lull_mod)) + maybe_send_keepalive(time(NULL), True); + + if (DEBUG_GTE(DELTASUM, 3)) { + rprintf(FINFO, + "chunk[%d] len=%d offset=%s sum1=%08x\n", + i, s->sums[i].len, big_num(s->sums[i].offset), + s->sums[i].sum1); + } + } + + s->flength = offset; + + return s; +} + +void successful_send(int ndx) +{ + char fname[MAXPATHLEN]; + char *failed_op; + struct file_struct *file; + struct file_list *flist; + STRUCT_STAT st; + + if (!remove_source_files) + return; + + flist = flist_for_ndx(ndx, "successful_send"); + file = flist->files[ndx - flist->ndx_start]; + if (!change_pathname(file, NULL, 0)) + return; + f_name(file, fname); + + if (do_lstat(fname, &st) < 0) { + failed_op = "re-lstat"; + goto failed; + } + + if (st.st_size != F_LENGTH(file) || st.st_mtime != file->modtime +#ifdef ST_MTIME_NSEC + || (NSEC_BUMP(file) && (uint32)st.ST_MTIME_NSEC != F_MOD_NSEC(file)) +#endif + ) { + rprintf(FERROR_XFER, "ERROR: Skipping sender remove for changed file: %s\n", fname); + return; + } + + if (do_unlink(fname) < 0) { + failed_op = "remove"; + failed: + if (errno == ENOENT) + rprintf(FINFO, "sender file already removed: %s\n", fname); + else + rsyserr(FERROR_XFER, errno, "sender failed to %s %s", failed_op, fname); + } else { + if (INFO_GTE(REMOVE, 1)) + rprintf(FINFO, "sender removed %s\n", fname); + } +} + +static void write_ndx_and_attrs(int f_out, int ndx, int iflags, + const char *fname, struct file_struct *file, + uchar fnamecmp_type, char *buf, int len) +{ + write_ndx(f_out, ndx); + if (protocol_version < 29) + return; + write_shortint(f_out, iflags); + if (iflags & ITEM_BASIS_TYPE_FOLLOWS) + write_byte(f_out, fnamecmp_type); + if (iflags & ITEM_XNAME_FOLLOWS) + write_vstring(f_out, buf, len); +#ifdef SUPPORT_XATTRS + if (preserve_xattrs && iflags & ITEM_REPORT_XATTR && do_xfers + && !(want_xattr_optim && BITS_SET(iflags, ITEM_XNAME_FOLLOWS|ITEM_LOCAL_CHANGE))) + send_xattr_request(fname, file, f_out); +#endif +} + +void send_files(int f_in, int f_out) +{ + int fd = -1; + struct sum_struct *s; + struct map_struct *mbuf = NULL; + STRUCT_STAT st; + char fname[MAXPATHLEN], xname[MAXPATHLEN]; + const char *path, *slash; + uchar fnamecmp_type; + int iflags, xlen; + struct file_struct *file; + int phase = 0, max_phase = protocol_version >= 29 ? 2 : 1; + int itemizing = am_server ? logfile_format_has_i : stdout_format_has_i; + enum logcode log_code = log_before_transfer ? FLOG : FINFO; + int f_xfer = write_batch < 0 ? batch_fd : f_out; + int save_io_error = io_error; + int ndx, j; + + if (DEBUG_GTE(SEND, 1)) + rprintf(FINFO, "send_files starting\n"); + + while (1) { + if (inc_recurse) { + send_extra_file_list(f_out, MIN_FILECNT_LOOKAHEAD); + extra_flist_sending_enabled = !flist_eof; + } + + /* This call also sets cur_flist. */ + ndx = read_ndx_and_attrs(f_in, f_out, &iflags, &fnamecmp_type, + xname, &xlen); + extra_flist_sending_enabled = False; + + if (ndx == NDX_DONE) { + if (!am_server && INFO_GTE(PROGRESS, 2) && cur_flist) { + set_current_file_index(NULL, 0); + end_progress(0); + } + if (inc_recurse && first_flist) { + file_old_total -= first_flist->used; + flist_free(first_flist); + if (first_flist) { + if (first_flist == cur_flist) + file_old_total = cur_flist->used; + write_ndx(f_out, NDX_DONE); + continue; + } + } + if (++phase > max_phase) + break; + if (DEBUG_GTE(SEND, 1)) + rprintf(FINFO, "send_files phase=%d\n", phase); + write_ndx(f_out, NDX_DONE); + continue; + } + + if (inc_recurse) + send_extra_file_list(f_out, MIN_FILECNT_LOOKAHEAD); + + if (ndx - cur_flist->ndx_start >= 0) + file = cur_flist->files[ndx - cur_flist->ndx_start]; + else + file = dir_flist->files[cur_flist->parent_ndx]; + if (F_PATHNAME(file)) { + path = F_PATHNAME(file); + slash = "/"; + } else { + path = slash = ""; + } + if (!change_pathname(file, NULL, 0)) + continue; + f_name(file, fname); + + if (DEBUG_GTE(SEND, 1)) + rprintf(FINFO, "send_files(%d, %s%s%s)\n", ndx, path,slash,fname); + +#ifdef SUPPORT_XATTRS + if (preserve_xattrs && iflags & ITEM_REPORT_XATTR && do_xfers + && !(want_xattr_optim && BITS_SET(iflags, ITEM_XNAME_FOLLOWS|ITEM_LOCAL_CHANGE))) + recv_xattr_request(file, f_in); +#endif + + if (!(iflags & ITEM_TRANSFER)) { + maybe_log_item(file, iflags, itemizing, xname); + write_ndx_and_attrs(f_out, ndx, iflags, fname, file, + fnamecmp_type, xname, xlen); + if (iflags & ITEM_IS_NEW) { + stats.created_files++; + if (S_ISREG(file->mode)) { + /* Nothing further to count. */ + } else if (S_ISDIR(file->mode)) + stats.created_dirs++; +#ifdef SUPPORT_LINKS + else if (S_ISLNK(file->mode)) + stats.created_symlinks++; +#endif + else if (IS_DEVICE(file->mode)) + stats.created_devices++; + else + stats.created_specials++; + } + continue; + } + if (phase == 2) { + rprintf(FERROR, + "got transfer request in phase 2 [%s]\n", + who_am_i()); + exit_cleanup(RERR_PROTOCOL); + } + + if (file->flags & FLAG_FILE_SENT) { + if (csum_length == SHORT_SUM_LENGTH) { + /* For inplace: redo phase turns off the backup + * flag so that we do a regular inplace send. */ + make_backups = -make_backups; + append_mode = -append_mode; + csum_length = SUM_LENGTH; + } + } else { + if (csum_length != SHORT_SUM_LENGTH) { + make_backups = -make_backups; + append_mode = -append_mode; + csum_length = SHORT_SUM_LENGTH; + } + if (iflags & ITEM_IS_NEW) + stats.created_files++; + } + + updating_basis_file = inplace && (protocol_version >= 29 + ? fnamecmp_type == FNAMECMP_FNAME : make_backups <= 0); + + if (!am_server && INFO_GTE(PROGRESS, 1)) + set_current_file_index(file, ndx); + stats.xferred_files++; + stats.total_transferred_size += F_LENGTH(file); + + if (!log_before_transfer) + remember_initial_stats(); + + if (!do_xfers) { /* log the transfer */ + log_item(FCLIENT, file, iflags, NULL); + write_ndx_and_attrs(f_out, ndx, iflags, fname, file, + fnamecmp_type, xname, xlen); + continue; + } + + if (!(s = receive_sums(f_in))) { + io_error |= IOERR_GENERAL; + rprintf(FERROR_XFER, "receive_sums failed\n"); + exit_cleanup(RERR_PROTOCOL); + } + + fd = do_open(fname, O_RDONLY, 0); + if (fd == -1) { + if (errno == ENOENT) { + enum logcode c = am_daemon + && protocol_version < 28 ? FERROR + : FWARNING; + io_error |= IOERR_VANISHED; + rprintf(c, "file has vanished: %s\n", + full_fname(fname)); + } else { + io_error |= IOERR_GENERAL; + rsyserr(FERROR_XFER, errno, + "send_files failed to open %s", + full_fname(fname)); + } + free_sums(s); + if (protocol_version >= 30) + send_msg_int(MSG_NO_SEND, ndx); + continue; + } + + /* map the local file */ + if (do_fstat(fd, &st) != 0) { + io_error |= IOERR_GENERAL; + rsyserr(FERROR_XFER, errno, "fstat failed"); + free_sums(s); + close(fd); + exit_cleanup(RERR_FILEIO); + } + + if (st.st_size) { + int32 read_size = MAX(s->blength * 3, MAX_MAP_SIZE); + mbuf = map_file(fd, st.st_size, read_size, s->blength); + } else + mbuf = NULL; + + if (DEBUG_GTE(DELTASUM, 2)) { + rprintf(FINFO, "send_files mapped %s%s%s of size %s\n", + path,slash,fname, big_num(st.st_size)); + } + + write_ndx_and_attrs(f_out, ndx, iflags, fname, file, + fnamecmp_type, xname, xlen); + write_sum_head(f_xfer, s); + + if (DEBUG_GTE(DELTASUM, 2)) + rprintf(FINFO, "calling match_sums %s%s%s\n", path,slash,fname); + + if (log_before_transfer) + log_item(FCLIENT, file, iflags, NULL); + else if (!am_server && INFO_GTE(NAME, 1) && INFO_EQ(PROGRESS, 1)) + rprintf(FCLIENT, "%s\n", fname); + + set_compression(fname); + + match_sums(f_xfer, s, mbuf, st.st_size); + if (INFO_GTE(PROGRESS, 1)) + end_progress(st.st_size); + + log_item(log_code, file, iflags, NULL); + + if (mbuf) { + j = unmap_file(mbuf); + if (j) { + io_error |= IOERR_GENERAL; + rsyserr(FERROR_XFER, j, + "read errors mapping %s", + full_fname(fname)); + } + } + close(fd); + + free_sums(s); + + if (DEBUG_GTE(SEND, 1)) + rprintf(FINFO, "sender finished %s%s%s\n", path,slash,fname); + + /* Flag that we actually sent this entry. */ + file->flags |= FLAG_FILE_SENT; + } + if (make_backups < 0) + make_backups = -make_backups; + + if (io_error != save_io_error && protocol_version >= 30) + send_msg_int(MSG_IO_ERROR, io_error); + + if (DEBUG_GTE(SEND, 1)) + rprintf(FINFO, "send files finished\n"); + + match_report(); + + write_ndx(f_out, NDX_DONE); +} diff --git a/rsync/shconfig.in b/rsync/shconfig.in new file mode 100755 index 0000000..5d1fdc5 --- /dev/null +++ b/rsync/shconfig.in @@ -0,0 +1,15 @@ +#! /bin/sh + +# config.sh.in + +# This file is processed by config.status to produce config.status, +# containing autoconf-determined values needed by the test scripts. + +ECHO_T="@ECHO_T@" +ECHO_N="@ECHO_N@" +ECHO_C="@ECHO_C@" +HOST_OS="@host_os@" +SHELL_PATH="@SHELL_PATH@" +FAKEROOT_PATH="@FAKEROOT_PATH@" + +export ECHO_T ECHO_N ECHO_C HOST_OS SHELL_PATH FAKEROOT_PATH diff --git a/rsync/socket.c b/rsync/socket.c new file mode 100644 index 0000000..3f5786b --- /dev/null +++ b/rsync/socket.c @@ -0,0 +1,855 @@ +/* + * Socket functions used in rsync. + * + * Copyright (C) 1992-2001 Andrew Tridgell + * Copyright (C) 2001, 2002 Martin Pool + * Copyright (C) 2003-2014 Wayne Davison + * + * 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, visit the http://fsf.org website. + */ + +/* This file is now converted to use the new-style getaddrinfo() + * interface, which supports IPv6 but is also supported on recent + * IPv4-only machines. On systems that don't have that interface, we + * emulate it using the KAME implementation. */ + +#include "rsync.h" +#include "itypes.h" +#ifdef HAVE_NETINET_IN_SYSTM_H +#include +#endif +#ifdef HAVE_NETINET_IP_H +#include +#endif +#include + +extern char *bind_address; +extern char *sockopts; +extern int default_af_hint; +extern int connect_timeout; + +#ifdef HAVE_SIGACTION +static struct sigaction sigact; +#endif + +static int sock_exec(const char *prog); + +/* Establish a proxy connection on an open socket to a web proxy by using the + * CONNECT method. If proxy_user and proxy_pass are not NULL, they are used to + * authenticate to the proxy using the "Basic" proxy-authorization protocol. */ +static int establish_proxy_connection(int fd, char *host, int port, + char *proxy_user, char *proxy_pass) +{ + char *cp, buffer[1024]; + char *authhdr, authbuf[1024]; + int len; + + if (proxy_user && proxy_pass) { + stringjoin(buffer, sizeof buffer, + proxy_user, ":", proxy_pass, NULL); + len = strlen(buffer); + + if ((len*8 + 5) / 6 >= (int)sizeof authbuf - 3) { + rprintf(FERROR, + "authentication information is too long\n"); + return -1; + } + + base64_encode(buffer, len, authbuf, 1); + authhdr = "\r\nProxy-Authorization: Basic "; + } else { + *authbuf = '\0'; + authhdr = ""; + } + + snprintf(buffer, sizeof buffer, "CONNECT %s:%d HTTP/1.0%s%s\r\n\r\n", + host, port, authhdr, authbuf); + len = strlen(buffer); + if (write(fd, buffer, len) != len) { + rsyserr(FERROR, errno, "failed to write to proxy"); + return -1; + } + + for (cp = buffer; cp < &buffer[sizeof buffer - 1]; cp++) { + if (read(fd, cp, 1) != 1) { + rsyserr(FERROR, errno, "failed to read from proxy"); + return -1; + } + if (*cp == '\n') + break; + } + + if (*cp != '\n') + cp++; + *cp-- = '\0'; + if (*cp == '\r') + *cp = '\0'; + if (strncmp(buffer, "HTTP/", 5) != 0) { + rprintf(FERROR, "bad response from proxy -- %s\n", + buffer); + return -1; + } + for (cp = &buffer[5]; isDigit(cp) || *cp == '.'; cp++) {} + while (*cp == ' ') + cp++; + if (*cp != '2') { + rprintf(FERROR, "bad response from proxy -- %s\n", + buffer); + return -1; + } + /* throw away the rest of the HTTP header */ + while (1) { + for (cp = buffer; cp < &buffer[sizeof buffer - 1]; cp++) { + if (read(fd, cp, 1) != 1) { + rsyserr(FERROR, errno, + "failed to read from proxy"); + return -1; + } + if (*cp == '\n') + break; + } + if (cp > buffer && *cp == '\n') + cp--; + if (cp == buffer && (*cp == '\n' || *cp == '\r')) + break; + } + return 0; +} + + +/* Try to set the local address for a newly-created socket. + * Return -1 if this fails. */ +int try_bind_local(int s, int ai_family, int ai_socktype, + const char *bind_addr) +{ + int error; + struct addrinfo bhints, *bres_all, *r; + + memset(&bhints, 0, sizeof bhints); + bhints.ai_family = ai_family; + bhints.ai_socktype = ai_socktype; + bhints.ai_flags = AI_PASSIVE; + if ((error = getaddrinfo(bind_addr, NULL, &bhints, &bres_all))) { + rprintf(FERROR, RSYNC_NAME ": getaddrinfo %s: %s\n", + bind_addr, gai_strerror(error)); + return -1; + } + + for (r = bres_all; r; r = r->ai_next) { + if (bind(s, r->ai_addr, r->ai_addrlen) == -1) + continue; + freeaddrinfo(bres_all); + return s; + } + + /* no error message; there might be some problem that allows + * creation of the socket but not binding, perhaps if the + * machine has no ipv6 address of this name. */ + freeaddrinfo(bres_all); + return -1; +} + +/* connect() timeout handler based on alarm() */ +static RETSIGTYPE contimeout_handler(UNUSED(int val)) +{ + connect_timeout = -1; +} + +/* Open a socket to a tcp remote host with the specified port. + * + * Based on code from Warren. Proxy support by Stephen Rothwell. + * getaddrinfo() rewrite contributed by KAME.net. + * + * Now that we support IPv6 we need to look up the remote machine's address + * first, using af_hint to set a preference for the type of address. Then + * depending on whether it has v4 or v6 addresses we try to open a connection. + * + * The loop allows for machines with some addresses which may not be reachable, + * perhaps because we can't e.g. route ipv6 to that network but we can get ip4 + * packets through. + * + * bind_addr: local address to use. Normally NULL to bind the wildcard address. + * + * af_hint: address family, e.g. AF_INET or AF_INET6. */ +int open_socket_out(char *host, int port, const char *bind_addr, + int af_hint) +{ + int type = SOCK_STREAM; + int error, s, j, addr_cnt, *errnos; + struct addrinfo hints, *res0, *res; + char portbuf[10]; + char *h, *cp; + int proxied = 0; + char buffer[1024]; + char *proxy_user = NULL, *proxy_pass = NULL; + + /* if we have a RSYNC_PROXY env variable then redirect our + * connetcion via a web proxy at the given address. */ + h = getenv("RSYNC_PROXY"); + proxied = h != NULL && *h != '\0'; + + if (proxied) { + strlcpy(buffer, h, sizeof buffer); + + /* Is the USER:PASS@ prefix present? */ + if ((cp = strrchr(buffer, '@')) != NULL) { + *cp++ = '\0'; + /* The remainder is the HOST:PORT part. */ + h = cp; + + if ((cp = strchr(buffer, ':')) == NULL) { + rprintf(FERROR, + "invalid proxy specification: should be USER:PASS@HOST:PORT\n"); + return -1; + } + *cp++ = '\0'; + + proxy_user = buffer; + proxy_pass = cp; + } else { + /* The whole buffer is the HOST:PORT part. */ + h = buffer; + } + + if ((cp = strchr(h, ':')) == NULL) { + rprintf(FERROR, + "invalid proxy specification: should be HOST:PORT\n"); + return -1; + } + *cp++ = '\0'; + strlcpy(portbuf, cp, sizeof portbuf); + if (DEBUG_GTE(CONNECT, 1)) { + rprintf(FINFO, "connection via http proxy %s port %s\n", + h, portbuf); + } + } else { + snprintf(portbuf, sizeof portbuf, "%d", port); + h = host; + } + + memset(&hints, 0, sizeof hints); + hints.ai_family = af_hint; + hints.ai_socktype = type; + error = getaddrinfo(h, portbuf, &hints, &res0); + if (error) { + rprintf(FERROR, RSYNC_NAME ": getaddrinfo: %s %s: %s\n", + h, portbuf, gai_strerror(error)); + return -1; + } + + for (res = res0, addr_cnt = 0; res; res = res->ai_next, addr_cnt++) {} + errnos = new_array0(int, addr_cnt); + if (!errnos) + out_of_memory("open_socket_out"); + + s = -1; + /* Try to connect to all addresses for this machine until we get + * through. It might e.g. be multi-homed, or have both IPv4 and IPv6 + * addresses. We need to create a socket for each record, since the + * address record tells us what protocol to use to try to connect. */ + for (res = res0, j = 0; res; res = res->ai_next, j++) { + s = socket(res->ai_family, res->ai_socktype, res->ai_protocol); + if (s < 0) + continue; + + if (bind_addr + && try_bind_local(s, res->ai_family, type, + bind_addr) == -1) { + close(s); + s = -1; + continue; + } + if (connect_timeout > 0) { + SIGACTION(SIGALRM, contimeout_handler); + alarm(connect_timeout); + } + + set_socket_options(s, sockopts); + while (connect(s, res->ai_addr, res->ai_addrlen) < 0) { + if (connect_timeout < 0) + exit_cleanup(RERR_CONTIMEOUT); + if (errno == EINTR) + continue; + close(s); + s = -1; + break; + } + + if (connect_timeout > 0) + alarm(0); + + if (s < 0) { + errnos[j] = errno; + continue; + } + + if (proxied + && establish_proxy_connection(s, host, port, + proxy_user, proxy_pass) != 0) { + close(s); + s = -1; + continue; + } + if (DEBUG_GTE(CONNECT, 2)) { + char buf[2048]; + if ((error = getnameinfo(res->ai_addr, res->ai_addrlen, buf, sizeof buf, NULL, 0, NI_NUMERICHOST)) != 0) + snprintf(buf, sizeof buf, "*getnameinfo failure: %s*", gai_strerror(error)); + rprintf(FINFO, "Connected to %s (%s)\n", h, buf); + } + break; + } + + if (s < 0 || DEBUG_GTE(CONNECT, 2)) { + char buf[2048]; + for (res = res0, j = 0; res; res = res->ai_next, j++) { + if (errnos[j] == 0) + continue; + if ((error = getnameinfo(res->ai_addr, res->ai_addrlen, buf, sizeof buf, NULL, 0, NI_NUMERICHOST)) != 0) + snprintf(buf, sizeof buf, "*getnameinfo failure: %s*", gai_strerror(error)); + rsyserr(FERROR, errnos[j], "failed to connect to %s (%s)", h, buf); + } + if (s < 0) + s = -1; + } + + freeaddrinfo(res0); + free(errnos); + + return s; +} + + +/* Open an outgoing socket, but allow for it to be intercepted by + * $RSYNC_CONNECT_PROG, which will execute a program across a TCP + * socketpair rather than really opening a socket. + * + * We use this primarily in testing to detect TCP flow bugs, but not + * cause security problems by really opening remote connections. + * + * This is based on the Samba LIBSMB_PROG feature. + * + * bind_addr: local address to use. Normally NULL to get the stack default. */ +int open_socket_out_wrapped(char *host, int port, const char *bind_addr, + int af_hint) +{ + char *prog = getenv("RSYNC_CONNECT_PROG"); + + if (prog && strchr(prog, '%')) { + int hlen = strlen(host); + int len = strlen(prog) + 1; + char *f, *t; + for (f = prog; *f; f++) { + if (*f != '%') + continue; + /* Compute more than enough room. */ + if (f[1] == '%') + f++; + else + len += hlen; + } + f = prog; + if (!(prog = new_array(char, len))) + out_of_memory("open_socket_out_wrapped"); + for (t = prog; *f; f++) { + if (*f == '%') { + switch (*++f) { + case '%': + /* Just skips the extra '%'. */ + break; + case 'H': + memcpy(t, host, hlen); + t += hlen; + continue; + default: + f--; /* pass % through */ + break; + } + } + *t++ = *f; + } + *t = '\0'; + } + + if (DEBUG_GTE(CONNECT, 1)) { + rprintf(FINFO, "%sopening tcp connection to %s port %d\n", + prog ? "Using RSYNC_CONNECT_PROG instead of " : "", + host, port); + } + if (prog) + return sock_exec(prog); + return open_socket_out(host, port, bind_addr, af_hint); +} + + +/* Open one or more sockets for incoming data using the specified type, + * port, and address. + * + * The getaddrinfo() call may return several address results, e.g. for + * the machine's IPv4 and IPv6 name. + * + * We return an array of file-descriptors to the sockets, with a trailing + * -1 value to indicate the end of the list. + * + * bind_addr: local address to bind, or NULL to allow it to default. */ +static int *open_socket_in(int type, int port, const char *bind_addr, + int af_hint) +{ + int one = 1; + int s, *socks, maxs, i, ecnt; + struct addrinfo hints, *all_ai, *resp; + char portbuf[10], **errmsgs; + int error; + + memset(&hints, 0, sizeof hints); + hints.ai_family = af_hint; + hints.ai_socktype = type; + hints.ai_flags = AI_PASSIVE; + snprintf(portbuf, sizeof portbuf, "%d", port); + error = getaddrinfo(bind_addr, portbuf, &hints, &all_ai); + if (error) { + rprintf(FERROR, RSYNC_NAME ": getaddrinfo: bind address %s: %s\n", + bind_addr, gai_strerror(error)); + return NULL; + } + + /* Count max number of sockets we might open. */ + for (maxs = 0, resp = all_ai; resp; resp = resp->ai_next, maxs++) {} + + socks = new_array(int, maxs + 1); + errmsgs = new_array(char *, maxs); + if (!socks || !errmsgs) + out_of_memory("open_socket_in"); + + /* We may not be able to create the socket, if for example the + * machine knows about IPv6 in the C library, but not in the + * kernel. */ + for (resp = all_ai, i = ecnt = 0; resp; resp = resp->ai_next) { + s = socket(resp->ai_family, resp->ai_socktype, + resp->ai_protocol); + + if (s == -1) { + int r = asprintf(&errmsgs[ecnt++], + "socket(%d,%d,%d) failed: %s\n", + (int)resp->ai_family, (int)resp->ai_socktype, + (int)resp->ai_protocol, strerror(errno)); + if (r < 0) + out_of_memory("open_socket_in"); + /* See if there's another address that will work... */ + continue; + } + + setsockopt(s, SOL_SOCKET, SO_REUSEADDR, + (char *)&one, sizeof one); + if (sockopts) + set_socket_options(s, sockopts); + else + set_socket_options(s, lp_socket_options()); + +#ifdef IPV6_V6ONLY + if (resp->ai_family == AF_INET6) { + if (setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, + (char *)&one, sizeof one) < 0 + && default_af_hint != AF_INET6) { + close(s); + continue; + } + } +#endif + + /* Now we've got a socket - we need to bind it. */ + if (bind(s, resp->ai_addr, resp->ai_addrlen) < 0) { + /* Nope, try another */ + int r = asprintf(&errmsgs[ecnt++], + "bind() failed: %s (address-family %d)\n", + strerror(errno), (int)resp->ai_family); + if (r < 0) + out_of_memory("open_socket_in"); + close(s); + continue; + } + + socks[i++] = s; + } + socks[i] = -1; + + if (all_ai) + freeaddrinfo(all_ai); + + /* Only output the socket()/bind() messages if we were totally + * unsuccessful, or if the daemon is being run with -vv. */ + for (s = 0; s < ecnt; s++) { + if (!i || DEBUG_GTE(BIND, 1)) + rwrite(FLOG, errmsgs[s], strlen(errmsgs[s]), 0); + free(errmsgs[s]); + } + free(errmsgs); + + if (!i) { + rprintf(FERROR, + "unable to bind any inbound sockets on port %d\n", + port); + free(socks); + return NULL; + } + return socks; +} + + +/* Determine if a file descriptor is in fact a socket. */ +int is_a_socket(int fd) +{ + int v; + socklen_t l = sizeof (int); + + /* Parameters to getsockopt, setsockopt etc are very + * unstandardized across platforms, so don't be surprised if + * there are compiler warnings on e.g. SCO OpenSwerver or AIX. + * It seems they all eventually get the right idea. + * + * Debian says: ``The fifth argument of getsockopt and + * setsockopt is in reality an int [*] (and this is what BSD + * 4.* and libc4 and libc5 have). Some POSIX confusion + * resulted in the present socklen_t. The draft standard has + * not been adopted yet, but glibc2 already follows it and + * also has socklen_t [*]. See also accept(2).'' + * + * We now return to your regularly scheduled programming. */ + return getsockopt(fd, SOL_SOCKET, SO_TYPE, (char *)&v, &l) == 0; +} + + +static RETSIGTYPE sigchld_handler(UNUSED(int val)) +{ +#ifdef WNOHANG + while (waitpid(-1, NULL, WNOHANG) > 0) {} +#endif +#ifndef HAVE_SIGACTION + signal(SIGCHLD, sigchld_handler); +#endif +} + + +void start_accept_loop(int port, int (*fn)(int, int)) +{ + fd_set deffds; + int *sp, maxfd, i; + +#ifdef HAVE_SIGACTION + sigact.sa_flags = SA_NOCLDSTOP; +#endif + + /* open an incoming socket */ + sp = open_socket_in(SOCK_STREAM, port, bind_address, default_af_hint); + if (sp == NULL) + exit_cleanup(RERR_SOCKETIO); + + /* ready to listen */ + FD_ZERO(&deffds); + for (i = 0, maxfd = -1; sp[i] >= 0; i++) { + if (listen(sp[i], lp_listen_backlog()) < 0) { + rsyserr(FERROR, errno, "listen() on socket failed"); +#ifdef INET6 + if (errno == EADDRINUSE && i > 0) { + rprintf(FINFO, + "Try using --ipv4 or --ipv6 to avoid this listen() error.\n"); + } +#endif + exit_cleanup(RERR_SOCKETIO); + } + FD_SET(sp[i], &deffds); + if (maxfd < sp[i]) + maxfd = sp[i]; + } + + /* now accept incoming connections - forking a new process + * for each incoming connection */ + while (1) { + fd_set fds; + pid_t pid; + int fd; + struct sockaddr_storage addr; + socklen_t addrlen = sizeof addr; + + /* close log file before the potentially very long select so + * file can be trimmed by another process instead of growing + * forever */ + logfile_close(); + +#ifdef FD_COPY + FD_COPY(&deffds, &fds); +#else + fds = deffds; +#endif + + if (select(maxfd + 1, &fds, NULL, NULL, NULL) < 1) + continue; + + for (i = 0, fd = -1; sp[i] >= 0; i++) { + if (FD_ISSET(sp[i], &fds)) { + fd = accept(sp[i], (struct sockaddr *)&addr, + &addrlen); + break; + } + } + + if (fd < 0) + continue; + + SIGACTION(SIGCHLD, sigchld_handler); + + if ((pid = fork()) == 0) { + int ret; + for (i = 0; sp[i] >= 0; i++) + close(sp[i]); + /* Re-open log file in child before possibly giving + * up privileges (see logfile_close() above). */ + logfile_reopen(); + ret = fn(fd, fd); + close_all(); + _exit(ret); + } else if (pid < 0) { + rsyserr(FERROR, errno, + "could not create child server process"); + close(fd); + /* This might have happened because we're + * overloaded. Sleep briefly before trying to + * accept again. */ + sleep(2); + } else { + /* Parent doesn't need this fd anymore. */ + close(fd); + } + } +} + + +enum SOCK_OPT_TYPES {OPT_BOOL,OPT_INT,OPT_ON}; + +struct +{ + char *name; + int level; + int option; + int value; + int opttype; +} socket_options[] = { + {"SO_KEEPALIVE", SOL_SOCKET, SO_KEEPALIVE, 0, OPT_BOOL}, + {"SO_REUSEADDR", SOL_SOCKET, SO_REUSEADDR, 0, OPT_BOOL}, +#ifdef SO_BROADCAST + {"SO_BROADCAST", SOL_SOCKET, SO_BROADCAST, 0, OPT_BOOL}, +#endif +#ifdef TCP_NODELAY + {"TCP_NODELAY", IPPROTO_TCP, TCP_NODELAY, 0, OPT_BOOL}, +#endif +#ifdef IPTOS_LOWDELAY + {"IPTOS_LOWDELAY", IPPROTO_IP, IP_TOS, IPTOS_LOWDELAY, OPT_ON}, +#endif +#ifdef IPTOS_THROUGHPUT + {"IPTOS_THROUGHPUT", IPPROTO_IP, IP_TOS, IPTOS_THROUGHPUT, OPT_ON}, +#endif +#ifdef SO_SNDBUF + {"SO_SNDBUF", SOL_SOCKET, SO_SNDBUF, 0, OPT_INT}, +#endif +#ifdef SO_RCVBUF + {"SO_RCVBUF", SOL_SOCKET, SO_RCVBUF, 0, OPT_INT}, +#endif +#ifdef SO_SNDLOWAT + {"SO_SNDLOWAT", SOL_SOCKET, SO_SNDLOWAT, 0, OPT_INT}, +#endif +#ifdef SO_RCVLOWAT + {"SO_RCVLOWAT", SOL_SOCKET, SO_RCVLOWAT, 0, OPT_INT}, +#endif +#ifdef SO_SNDTIMEO + {"SO_SNDTIMEO", SOL_SOCKET, SO_SNDTIMEO, 0, OPT_INT}, +#endif +#ifdef SO_RCVTIMEO + {"SO_RCVTIMEO", SOL_SOCKET, SO_RCVTIMEO, 0, OPT_INT}, +#endif + {NULL,0,0,0,0} +}; + + +/* Set user socket options. */ +void set_socket_options(int fd, char *options) +{ + char *tok; + + if (!options || !*options) + return; + + options = strdup(options); + + if (!options) + out_of_memory("set_socket_options"); + + for (tok = strtok(options, " \t,"); tok; tok = strtok(NULL," \t,")) { + int ret=0,i; + int value = 1; + char *p; + int got_value = 0; + + if ((p = strchr(tok,'='))) { + *p = 0; + value = atoi(p+1); + got_value = 1; + } + + for (i = 0; socket_options[i].name; i++) { + if (strcmp(socket_options[i].name,tok)==0) + break; + } + + if (!socket_options[i].name) { + rprintf(FERROR,"Unknown socket option %s\n",tok); + continue; + } + + switch (socket_options[i].opttype) { + case OPT_BOOL: + case OPT_INT: + ret = setsockopt(fd,socket_options[i].level, + socket_options[i].option, + (char *)&value, sizeof (int)); + break; + + case OPT_ON: + if (got_value) + rprintf(FERROR,"syntax error -- %s does not take a value\n",tok); + + { + int on = socket_options[i].value; + ret = setsockopt(fd,socket_options[i].level, + socket_options[i].option, + (char *)&on, sizeof (int)); + } + break; + } + + if (ret != 0) { + rsyserr(FERROR, errno, + "failed to set socket option %s", tok); + } + } + + free(options); +} + + +/* This is like socketpair but uses tcp. The function guarantees that nobody + * else can attach to the socket, or if they do that this function fails and + * the socket gets closed. Returns 0 on success, -1 on failure. The resulting + * file descriptors are symmetrical. Currently only for RSYNC_CONNECT_PROG. */ +static int socketpair_tcp(int fd[2]) +{ + int listener; + struct sockaddr_in sock; + struct sockaddr_in sock2; + socklen_t socklen = sizeof sock; + int connect_done = 0; + + fd[0] = fd[1] = listener = -1; + + memset(&sock, 0, sizeof sock); + + if ((listener = socket(PF_INET, SOCK_STREAM, 0)) == -1) + goto failed; + + memset(&sock2, 0, sizeof sock2); +#ifdef HAVE_SOCKADDR_IN_LEN + sock2.sin_len = sizeof sock2; +#endif + sock2.sin_family = PF_INET; + sock2.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + if (bind(listener, (struct sockaddr *)&sock2, sizeof sock2) != 0 + || listen(listener, 1) != 0 + || getsockname(listener, (struct sockaddr *)&sock, &socklen) != 0 + || (fd[1] = socket(PF_INET, SOCK_STREAM, 0)) == -1) + goto failed; + + set_nonblocking(fd[1]); + + sock.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + if (connect(fd[1], (struct sockaddr *)&sock, sizeof sock) == -1) { + if (errno != EINPROGRESS) + goto failed; + } else + connect_done = 1; + + if ((fd[0] = accept(listener, (struct sockaddr *)&sock2, &socklen)) == -1) + goto failed; + + close(listener); + listener = -1; + + set_blocking(fd[1]); + + if (connect_done == 0) { + if (connect(fd[1], (struct sockaddr *)&sock, sizeof sock) != 0 + && errno != EISCONN) + goto failed; + } + + /* all OK! */ + return 0; + + failed: + if (fd[0] != -1) + close(fd[0]); + if (fd[1] != -1) + close(fd[1]); + if (listener != -1) + close(listener); + return -1; +} + + +/* Run a program on a local tcp socket, so that we can talk to it's stdin and + * stdout. This is used to fake a connection to a daemon for testing -- not + * for the normal case of running SSH. + * + * Retruns a socket which is attached to a subprocess running "prog". stdin and + * stdout are attached. stderr is left attached to the original stderr. */ +static int sock_exec(const char *prog) +{ + pid_t pid; + int fd[2]; + + if (socketpair_tcp(fd) != 0) { + rsyserr(FERROR, errno, "socketpair_tcp failed"); + return -1; + } + if (DEBUG_GTE(CMD, 1)) + rprintf(FINFO, "Running socket program: \"%s\"\n", prog); + + pid = fork(); + if (pid < 0) { + rsyserr(FERROR, errno, "fork"); + exit_cleanup(RERR_IPC); + } + + if (pid == 0) { + close(fd[0]); + if (dup2(fd[1], STDIN_FILENO) < 0 + || dup2(fd[1], STDOUT_FILENO) < 0) { + fprintf(stderr, "Failed to run \"%s\"\n", prog); + exit(1); + } + exit(system(prog)); + } + + close(fd[1]); + return fd[0]; +} diff --git a/rsync/stunnel-rsync.in b/rsync/stunnel-rsync.in new file mode 100755 index 0000000..fdd8bfd --- /dev/null +++ b/rsync/stunnel-rsync.in @@ -0,0 +1,52 @@ +#!/bin/bash +# This must be called as (note the trailing dot): +# +# stunnel-rsync HOSTNAME rsync --server --daemon . +# +# ... which is typically done via the rsync-ssl script, which results in something like this: +# +# rsync --rsh=stunnel-rsync -aiv HOSTNAME::module [ARGS] +# +# This SSL setup based on the files by: http://dozzie.jarowit.net/trac/wiki/RsyncSSL +# Note that this requires at least version 4.x of stunnel. + +# The current environment can override using the RSYNC_SSL_* values: +if [ x"$RSYNC_SSL_CERT" = x ]; then + cert="" +else + cert="cert = $RSYNC_SSL_CERT" +fi +if [ x"$RSYNC_SSL_CA_CERT" ]; then + cafile="" + verify=0 +else + cafile="CAfile = $RSYNC_SSL_CA_CERT" + verify=3 +fi +port=${RSYNC_SSL_PORT:-874} + +# If the user specified USER@HOSTNAME::module, then rsync passes us +# the -l USER option too, so we must be prepared to ignore it. +if [ x"$1" = x"-l" ]; then + shift 2 +fi + +hostname=$1 +shift + +if [ x"$hostname" = x -o x"$1" != x"rsync" -o x"$2" != x"--server" -o x"$3" != x"--daemon" ]; then + echo "Usage: stunnel-rsync HOSTNAME rsync --server --daemon ." 1>&2 + exit 1 +fi + +# devzero@web.de came up with this no-tmpfile calling syntax: +@stunnel4@ -fd 10 11<&0 < +#endif +#ifdef HAVE_SYS_ATTR_H +#include +#endif + +#if defined HAVE_SYS_FALLOCATE && !defined HAVE_FALLOCATE +#include +#endif + +extern int dry_run; +extern int am_root; +extern int am_sender; +extern int read_only; +extern int list_only; +extern int preserve_perms; +extern int preserve_executability; + +#define RETURN_ERROR_IF(x,e) \ + do { \ + if (x) { \ + errno = (e); \ + return -1; \ + } \ + } while (0) + +#define RETURN_ERROR_IF_RO_OR_LO RETURN_ERROR_IF(read_only || list_only, EROFS) + +int do_unlink(const char *fname) +{ + if (dry_run) return 0; + RETURN_ERROR_IF_RO_OR_LO; + return unlink(fname); +} + +#ifdef SUPPORT_LINKS +int do_symlink(const char *lnk, const char *fname) +{ + if (dry_run) return 0; + RETURN_ERROR_IF_RO_OR_LO; + +#if defined NO_SYMLINK_XATTRS || defined NO_SYMLINK_USER_XATTRS + /* For --fake-super, we create a normal file with mode 0600 + * and write the lnk into it. */ + if (am_root < 0) { + int ok, len = strlen(lnk); + int fd = open(fname, O_WRONLY|O_CREAT|O_TRUNC, S_IWUSR|S_IRUSR); + if (fd < 0) + return -1; + ok = write(fd, lnk, len) == len; + if (close(fd) < 0) + ok = 0; + return ok ? 0 : -1; + } +#endif + + return symlink(lnk, fname); +} + +#if defined NO_SYMLINK_XATTRS || defined NO_SYMLINK_USER_XATTRS +ssize_t do_readlink(const char *path, char *buf, size_t bufsiz) +{ + /* For --fake-super, we read the link from the file. */ + if (am_root < 0) { + int fd = do_open_nofollow(path, O_RDONLY); + if (fd >= 0) { + int len = read(fd, buf, bufsiz); + close(fd); + return len; + } + if (errno != ELOOP) + return -1; + /* A real symlink needs to be turned into a fake one on the receiving + * side, so tell the generator that the link has no length. */ + if (!am_sender) + return 0; + /* Otherwise fall through and let the sender report the real length. */ + } + + return readlink(path, buf, bufsiz); +} +#endif +#endif + +#ifdef HAVE_LINK +int do_link(const char *fname1, const char *fname2) +{ + if (dry_run) return 0; + RETURN_ERROR_IF_RO_OR_LO; + return link(fname1, fname2); +} +#endif + +int do_lchown(const char *path, uid_t owner, gid_t group) +{ + if (dry_run) return 0; + RETURN_ERROR_IF_RO_OR_LO; +#ifndef HAVE_LCHOWN +#define lchown chown +#endif + return lchown(path, owner, group); +} + +int do_mknod(const char *pathname, mode_t mode, dev_t dev) +{ + if (dry_run) return 0; + RETURN_ERROR_IF_RO_OR_LO; + + /* For --fake-super, we create a normal file with mode 0600. */ + if (am_root < 0) { + int fd = open(pathname, O_WRONLY|O_CREAT|O_TRUNC, S_IWUSR|S_IRUSR); + if (fd < 0 || close(fd) < 0) + return -1; + return 0; + } + +#if !defined MKNOD_CREATES_FIFOS && defined HAVE_MKFIFO + if (S_ISFIFO(mode)) + return mkfifo(pathname, mode); +#endif +#if !defined MKNOD_CREATES_SOCKETS && defined HAVE_SYS_UN_H + if (S_ISSOCK(mode)) { + int sock; + struct sockaddr_un saddr; + unsigned int len = strlcpy(saddr.sun_path, pathname, sizeof saddr.sun_path); + if (len >= sizeof saddr.sun_path) { + errno = ENAMETOOLONG; + return -1; + } +#ifdef HAVE_SOCKADDR_UN_LEN + saddr.sun_len = len + 1; +#endif + saddr.sun_family = AF_UNIX; + + if ((sock = socket(PF_UNIX, SOCK_STREAM, 0)) < 0 + || (unlink(pathname) < 0 && errno != ENOENT) + || (bind(sock, (struct sockaddr*)&saddr, sizeof saddr)) < 0) + return -1; + close(sock); +#ifdef HAVE_CHMOD + return do_chmod(pathname, mode); +#else + return 0; +#endif + } +#endif +#ifdef HAVE_MKNOD + return mknod(pathname, mode, dev); +#else + return -1; +#endif +} + +int do_rmdir(const char *pathname) +{ + if (dry_run) return 0; + RETURN_ERROR_IF_RO_OR_LO; + return rmdir(pathname); +} + +int do_open(const char *pathname, int flags, mode_t mode) +{ + if (flags != O_RDONLY) { + RETURN_ERROR_IF(dry_run, 0); + RETURN_ERROR_IF_RO_OR_LO; + } + + return open(pathname, flags | O_BINARY, mode); +} + +#ifdef HAVE_CHMOD +int do_chmod(const char *path, mode_t mode) +{ + int code; + if (dry_run) return 0; + RETURN_ERROR_IF_RO_OR_LO; +#ifdef HAVE_LCHMOD + code = lchmod(path, mode & CHMOD_BITS); +#else + if (S_ISLNK(mode)) { +# if defined HAVE_SETATTRLIST + struct attrlist attrList; + uint32_t m = mode & CHMOD_BITS; /* manpage is wrong: not mode_t! */ + + memset(&attrList, 0, sizeof attrList); + attrList.bitmapcount = ATTR_BIT_MAP_COUNT; + attrList.commonattr = ATTR_CMN_ACCESSMASK; + code = setattrlist(path, &attrList, &m, sizeof m, FSOPT_NOFOLLOW); +# else + code = 1; +# endif + } else + code = chmod(path, mode & CHMOD_BITS); /* DISCOURAGED FUNCTION */ +#endif /* !HAVE_LCHMOD */ + if (code != 0 && (preserve_perms || preserve_executability)) + return code; + return 0; +} +#endif + +int do_rename(const char *fname1, const char *fname2) +{ + if (dry_run) return 0; + RETURN_ERROR_IF_RO_OR_LO; + return rename(fname1, fname2); +} + +#ifdef HAVE_FTRUNCATE +int do_ftruncate(int fd, OFF_T size) +{ + int ret; + + if (dry_run) return 0; + RETURN_ERROR_IF_RO_OR_LO; + + do { + ret = ftruncate(fd, size); + } while (ret < 0 && errno == EINTR); + + return ret; +} +#endif + +void trim_trailing_slashes(char *name) +{ + int l; + /* Some BSD systems cannot make a directory if the name + * contains a trailing slash. + * */ + + /* Don't change empty string; and also we can't improve on + * "/" */ + + l = strlen(name); + while (l > 1) { + if (name[--l] != '/') + break; + name[l] = '\0'; + } +} + +int do_mkdir(char *fname, mode_t mode) +{ + if (dry_run) return 0; + RETURN_ERROR_IF_RO_OR_LO; + trim_trailing_slashes(fname); + return mkdir(fname, mode); +} + +/* like mkstemp but forces permissions */ +int do_mkstemp(char *template, mode_t perms) +{ + RETURN_ERROR_IF(dry_run, 0); + RETURN_ERROR_IF(read_only, EROFS); + perms |= S_IWUSR; + +#if defined HAVE_SECURE_MKSTEMP && defined HAVE_FCHMOD && (!defined HAVE_OPEN64 || defined HAVE_MKSTEMP64) + { + int fd = mkstemp(template); + if (fd == -1) + return -1; + if (fchmod(fd, perms) != 0 && preserve_perms) { + int errno_save = errno; + close(fd); + unlink(template); + errno = errno_save; + return -1; + } +#if defined HAVE_SETMODE && O_BINARY + setmode(fd, O_BINARY); +#endif + return fd; + } +#else + if (!mktemp(template)) + return -1; + return do_open(template, O_RDWR|O_EXCL|O_CREAT, perms); +#endif +} + +int do_stat(const char *fname, STRUCT_STAT *st) +{ +#ifdef USE_STAT64_FUNCS + return stat64(fname, st); +#else + return stat(fname, st); +#endif +} + +int do_lstat(const char *fname, STRUCT_STAT *st) +{ +#ifdef SUPPORT_LINKS +# ifdef USE_STAT64_FUNCS + return lstat64(fname, st); +# else + return lstat(fname, st); +# endif +#else + return do_stat(fname, st); +#endif +} + +int do_fstat(int fd, STRUCT_STAT *st) +{ +#ifdef USE_STAT64_FUNCS + return fstat64(fd, st); +#else + return fstat(fd, st); +#endif +} + +OFF_T do_lseek(int fd, OFF_T offset, int whence) +{ +#ifdef HAVE_LSEEK64 +#if !SIZEOF_OFF64_T + OFF_T lseek64(); +#else + off64_t lseek64(); +#endif + return lseek64(fd, offset, whence); +#else + return lseek(fd, offset, whence); +#endif +} + +#ifdef HAVE_UTIMENSAT +int do_utimensat(const char *fname, time_t modtime, uint32 mod_nsec) +{ + struct timespec t[2]; + + if (dry_run) return 0; + RETURN_ERROR_IF_RO_OR_LO; + + t[0].tv_sec = 0; + t[0].tv_nsec = UTIME_NOW; + t[1].tv_sec = modtime; + t[1].tv_nsec = mod_nsec; + return utimensat(AT_FDCWD, fname, t, AT_SYMLINK_NOFOLLOW); +} +#endif + +#ifdef HAVE_LUTIMES +int do_lutimes(const char *fname, time_t modtime, uint32 mod_nsec) +{ + struct timeval t[2]; + + if (dry_run) return 0; + RETURN_ERROR_IF_RO_OR_LO; + + t[0].tv_sec = time(NULL); + t[0].tv_usec = 0; + t[1].tv_sec = modtime; + t[1].tv_usec = mod_nsec / 1000; + return lutimes(fname, t); +} +#endif + +#ifdef HAVE_UTIMES +int do_utimes(const char *fname, time_t modtime, uint32 mod_nsec) +{ + struct timeval t[2]; + + if (dry_run) return 0; + RETURN_ERROR_IF_RO_OR_LO; + + t[0].tv_sec = time(NULL); + t[0].tv_usec = 0; + t[1].tv_sec = modtime; + t[1].tv_usec = mod_nsec / 1000; + return utimes(fname, t); +} + +#elif defined HAVE_UTIME +int do_utime(const char *fname, time_t modtime, UNUSED(uint32 mod_nsec)) +{ +#ifdef HAVE_STRUCT_UTIMBUF + struct utimbuf tbuf; +#else + time_t t[2]; +#endif + + if (dry_run) return 0; + RETURN_ERROR_IF_RO_OR_LO; + +# ifdef HAVE_STRUCT_UTIMBUF + tbuf.actime = time(NULL); + tbuf.modtime = modtime; + return utime(fname, &tbuf); +# else + t[0] = time(NULL); + t[1] = modtime; + return utime(fname, t); +# endif +} + +#else +#error Need utimes or utime function. +#endif + +#ifdef SUPPORT_PREALLOCATION +int do_fallocate(int fd, OFF_T offset, OFF_T length) +{ +#ifdef FALLOC_FL_KEEP_SIZE +#define DO_FALLOC_OPTIONS FALLOC_FL_KEEP_SIZE +#else +#define DO_FALLOC_OPTIONS 0 +#endif + RETURN_ERROR_IF(dry_run, 0); + RETURN_ERROR_IF_RO_OR_LO; +#if defined HAVE_FALLOCATE + return fallocate(fd, DO_FALLOC_OPTIONS, offset, length); +#elif defined HAVE_SYS_FALLOCATE + return syscall(SYS_fallocate, fd, DO_FALLOC_OPTIONS, (loff_t)offset, (loff_t)length); +#elif defined HAVE_EFFICIENT_POSIX_FALLOCATE + return posix_fallocate(fd, offset, length); +#else +#error Coding error in SUPPORT_PREALLOCATION logic. +#endif +} +#endif + +int do_open_nofollow(const char *pathname, int flags) +{ +#ifndef O_NOFOLLOW + STRUCT_STAT f_st, l_st; +#endif + int fd; + + if (flags != O_RDONLY) { + RETURN_ERROR_IF(dry_run, 0); + RETURN_ERROR_IF_RO_OR_LO; +#ifndef O_NOFOLLOW + /* This function doesn't support write attempts w/o O_NOFOLLOW. */ + errno = EINVAL; + return -1; +#endif + } + +#ifdef O_NOFOLLOW + fd = open(pathname, flags|O_NOFOLLOW); +#else + if (do_lstat(pathname, &l_st) < 0) + return -1; + if (S_ISLNK(l_st.st_mode)) { + errno = ELOOP; + return -1; + } + if ((fd = open(pathname, flags)) < 0) + return fd; + if (do_fstat(fd, &f_st) < 0) { + close_and_return_error: + { + int save_errno = errno; + close(fd); + errno = save_errno; + } + return -1; + } + if (l_st.st_dev != f_st.st_dev || l_st.st_ino != f_st.st_ino) { + errno = EINVAL; + goto close_and_return_error; + } +#endif + + return fd; +} diff --git a/rsync/t_stub.c b/rsync/t_stub.c new file mode 100644 index 0000000..c230396 --- /dev/null +++ b/rsync/t_stub.c @@ -0,0 +1,99 @@ +/* + * This file contains really simple implementations for rsync global + * functions, so that module test harnesses can run standalone. + * + * Copyright (C) 2001, 2002 Martin Pool + * Copyright (C) 2003-2014 Wayne Davison + * + * 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, visit the http://fsf.org website. + */ + +#include "rsync.h" + +int modify_window = 0; +int preallocate_files = 0; +int protect_args = 0; +int module_id = -1; +int checksum_len = 0; +int relative_paths = 0; +int module_dirlen = 0; +int preserve_acls = 0; +int preserve_times = 0; +int preserve_xattrs = 0; +char *partial_dir; +char *module_dir; +filter_rule_list daemon_filter_list; + + void rprintf(UNUSED(enum logcode code), const char *format, ...) +{ + va_list ap; + va_start(ap, format); + vfprintf(stderr, format, ap); + va_end(ap); +} + + void rsyserr(UNUSED(enum logcode code), int errcode, const char *format, ...) +{ + va_list ap; + fputs(RSYNC_NAME ": ", stderr); + va_start(ap, format); + vfprintf(stderr, format, ap); + va_end(ap); + fprintf(stderr, ": %s (%d)\n", strerror(errcode), errcode); +} + + void _exit_cleanup(int code, const char *file, int line) +{ + fprintf(stderr, "exit(%d): %s(%d)\n", + code, file, line); + exit(code); +} + + int check_filter(UNUSED(filter_rule_list *listp), UNUSED(enum logcode code), + UNUSED(const char *name), UNUSED(int name_is_dir)) +{ + /* This function doesn't really get called in this test context, so + * just return 0. */ + return 0; +} + + int copy_xattrs(UNUSED(const char *source), UNUSED(const char *dest)) +{ + return -1; +} + + void free_xattr(UNUSED(stat_x *sxp)) +{ + return; +} + + void free_acl(UNUSED(stat_x *sxp)) +{ + return; +} + + char *lp_name(UNUSED(int mod)) +{ + return NULL; +} + + BOOL lp_use_chroot(UNUSED(int mod)) +{ + return 0; +} + + const char *who_am_i(void) +{ + return "tester"; +} diff --git a/rsync/t_unsafe.c b/rsync/t_unsafe.c new file mode 100644 index 0000000..72339d0 --- /dev/null +++ b/rsync/t_unsafe.c @@ -0,0 +1,48 @@ +/* + * Test harness for unsafe_symlink(). Not linked into rsync itself. + * + * Copyright (C) 2002 Martin Pool + * Copyright (C) 2003-2014 Wayne Davison + * + * 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, visit the http://fsf.org website. + */ + +/* Prints either "safe" or "unsafe" depending on the two arguments. + * Always returns 0 unless something extraordinary happens. */ + +#include "rsync.h" + +int dry_run = 0; +int am_root = 0; +int am_sender = 1; +int read_only = 0; +int list_only = 0; +int human_readable = 0; +int preserve_perms = 0; +int preserve_executability = 0; +short info_levels[COUNT_INFO], debug_levels[COUNT_DEBUG]; + +int +main(int argc, char **argv) +{ + if (argc != 3) { + fprintf(stderr, "usage: t_unsafe LINKDEST SRCDIR\n"); + return 1; + } + + printf("%s\n", + unsafe_symlink(argv[1], argv[2]) ? "unsafe" : "safe"); + + return 0; +} diff --git a/rsync/tech_report.tex b/rsync/tech_report.tex new file mode 100644 index 0000000..4144990 --- /dev/null +++ b/rsync/tech_report.tex @@ -0,0 +1,310 @@ +\documentclass[a4paper]{article} +\begin{document} + + +\title{The rsync algorithm} + +\author{Andrew Tridgell \quad\quad Paul Mackerras\\ +Department of Computer Science \\ +Australian National University \\ +Canberra, ACT 0200, Australia} + +\maketitle + +\begin{abstract} + This report presents an algorithm for updating a file on one machine + to be identical to a file on another machine. We assume that the + two machines are connected by a low-bandwidth high-latency + bi-directional communications link. The algorithm identifies parts + of the source file which are identical to some part of the + destination file, and only sends those parts which cannot be matched + in this way. Effectively, the algorithm computes a set of + differences without having both files on the same machine. The + algorithm works best when the files are similar, but will also + function correctly and reasonably efficiently when the files are + quite different. +\end{abstract} + +\section{The problem} + +Imagine you have two files, $A$ and $B$, and you wish to update $B$ to be +the same as $A$. The obvious method is to copy $A$ onto $B$. + +Now imagine that the two files are on machines connected by a slow +communications link, for example a dialup IP link. If $A$ is large, +copying $A$ onto $B$ will be slow. To make it faster you could +compress $A$ before sending it, but that will usually only gain a +factor of 2 to 4. + +Now assume that $A$ and $B$ are quite similar, perhaps both derived +from the same original file. To really speed things up you would need +to take advantage of this similarity. A common method is to send just +the differences between $A$ and $B$ down the link and then use this +list of differences to reconstruct the file. + +The problem is that the normal methods for creating a set of +differences between two files rely on being able to read both files. +Thus they require that both files are available beforehand at one end +of the link. If they are not both available on the same machine, +these algorithms cannot be used (once you had copied the file over, +you wouldn't need the differences). This is the problem that rsync +addresses. + +The rsync algorithm efficiently computes which parts of a source file +match some part of an existing destination file. These parts need not +be sent across the link; all that is needed is a reference to the part +of the destination file. Only parts of the source file which are not +matched in this way need to be sent verbatim. The receiver can then +construct a copy of the source file using the references to parts of +the existing destination file and the verbatim material. + +Trivially, the data sent to the receiver can be compressed using any +of a range of common compression algorithms, for further speed +improvements. + +\section{The rsync algorithm} + +Suppose we have two general purpose computers $\alpha$ and $\beta$. +Computer $\alpha$ has access to a file $A$ and $\beta$ has access to +file $B$, where $A$ and $B$ are ``similar''. There is a slow +communications link between $\alpha$ and $\beta$. + +The rsync algorithm consists of the following steps: + +\begin{enumerate} +\item $\beta$ splits the file $B$ into a series of non-overlapping + fixed-sized blocks of size S bytes\footnote{We have found that + values of S between 500 and 1000 are quite good for most purposes}. + The last block may be shorter than $S$ bytes. + +\item For each of these blocks $\beta$ calculates two checksums: + a weak ``rolling'' 32-bit checksum (described below) and a strong + 128-bit MD4 checksum. + +\item $\beta$ sends these checksums to $\alpha$. + +\item $\alpha$ searches through $A$ to find all blocks of length $S$ + bytes (at any offset, not just multiples of $S$) that have the same + weak and strong checksum as one of the blocks of $B$. This can be + done in a single pass very quickly using a special property of the + rolling checksum described below. + +\item $\alpha$ sends $\beta$ a sequence of instructions for + constructing a copy of $A$. Each instruction is either a reference + to a block of $B$, or literal data. Literal data is sent only for + those sections of $A$ which did not match any of the blocks of $B$. +\end{enumerate} + +The end result is that $\beta$ gets a copy of $A$, but only the pieces +of $A$ that are not found in $B$ (plus a small amount of data for +checksums and block indexes) are sent over the link. The algorithm +also only requires one round trip, which minimises the impact of the +link latency. + +The most important details of the algorithm are the rolling checksum +and the associated multi-alternate search mechanism which allows the +all-offsets checksum search to proceed very quickly. These will be +discussed in greater detail below. + +\section{Rolling checksum} + +The weak rolling checksum used in the rsync algorithm needs to have +the property that it is very cheap to calculate the checksum of a +buffer $X_2 .. X_{n+1}$ given the checksum of buffer $X_1 .. X_n$ and +the values of the bytes $X_1$ and $X_{n+1}$. + +The weak checksum algorithm we used in our implementation was inspired +by Mark Adler's adler-32 checksum. Our checksum is defined by +$$ a(k,l) = (\sum_{i=k}^l X_i) \bmod M $$ +$$ b(k,l) = (\sum_{i=k}^l (l-i+1)X_i) \bmod M $$ +$$ s(k,l) = a(k,l) + 2^{16} b(k,l) $$ + +where $s(k,l)$ is the rolling checksum of the bytes $X_k \ldots X_l$. +For simplicity and speed, we use $M = 2^{16}$. + +The important property of this checksum is that successive values can +be computed very efficiently using the recurrence relations + +$$ a(k+1,l+1) = (a(k,l) - X_k + X_{l+1}) \bmod M $$ +$$ b(k+1,l+1) = (b(k,l) - (l-k+1) X_k + a(k+1,l+1)) \bmod M $$ + +Thus the checksum can be calculated for blocks of length S at all +possible offsets within a file in a ``rolling'' fashion, with very +little computation at each point. + +Despite its simplicity, this checksum was found to be quite adequate as +a first-level check for a match of two file blocks. We have found in +practice that the probability of this checksum matching when the +blocks are not equal is quite low. This is important because the much +more expensive strong checksum must be calculated for each block where +the weak checksum matches. + +\section{Checksum searching} + +Once $\alpha$ has received the list of checksums of the blocks of $B$, +it must search $A$ for any blocks at any offset that match the +checksum of some block of $B$. The basic strategy is to compute the +32-bit rolling checksum for a block of length $S$ starting at each +byte of $A$ in turn, and for each checksum, search the list for a +match. To do this our implementation uses a +simple 3 level searching scheme. + +The first level uses a 16-bit hash of the 32-bit rolling checksum and +a $2^{16}$ entry hash table. The list of checksum values (i.e., the +checksums from the blocks of $B$) is sorted according to the 16-bit +hash of the 32-bit rolling checksum. Each entry in the hash table +points to the first element of the list for that hash value, or +contains a null value if no element of the list has that hash value. + +At each offset in the file the 32-bit rolling checksum and its 16-bit +hash are calculated. If the hash table entry for that hash value is +not a null value, the second-level check is invoked. + +The second-level check involves scanning the sorted checksum list +starting with the entry pointed to by the hash table entry, looking +for an entry whose 32-bit rolling checksum matches the current value. +The scan terminates when it reaches an entry whose 16-bit hash +differs. If this search finds a match, the third-level check is +invoked. + +The third-level check involves calculating the strong checksum for the +current offset in the file and comparing it with the strong checksum +value in the current list entry. If the two strong checksums match, +we assume that we have found a block of $A$ which matches a block of +$B$. In fact the blocks could be different, but the probability of +this is microscopic, and in practice this is a reasonable assumption. + +When a match is found, $\alpha$ sends $\beta$ the data in $A$ between +the current file offset and the end of the previous match, followed by +the index of the block in $B$ that matched. This data is sent +immediately a match is found, which allows us to overlap the +communication with further computation. + +If no match is found at a given offset in the file, the rolling +checksum is updated to the next offset and the search proceeds. If a +match is found, the search is restarted at the end of the matched +block. This strategy saves a considerable amount of computation for +the common case where the two files are nearly identical. In +addition, it would be a simple matter to encode the block indexes as +runs, for the common case where a portion of $A$ matches a series of +blocks of $B$ in order. + +\section{Pipelining} + +The above sections describe the process for constructing a copy of one +file on a remote system. If we have a several files to copy, we can +gain a considerable latency advantage by pipelining the process. + +This involves $\beta$ initiating two independent processes. One of the +processes generates and sends the checksums to $\alpha$ while the +other receives the difference information from $\alpha$ and +reconstructs the files. + +If the communications link is buffered then these two processes can +proceed independently and the link should be kept fully utilised in +both directions for most of the time. + +\section{Results} + +To test the algorithm, tar files were created of the Linux kernel +sources for two versions of the kernel. The two kernel versions were +1.99.10 and 2.0.0. These tar files are approximately 24MB in size and +are separated by 5 released patch levels. + +Out of the 2441 files in the 1.99.10 release, 291 files had changed in +the 2.0.0 release, 19 files had been removed and 25 files had been +added. + +A ``diff'' of the two tar files using the standard GNU diff utility +produced over 32 thousand lines of output totalling 2.1 MB. + +The following table shows the results for rsync between the two files +with a varying block size.\footnote{All the tests in this section were + carried out using rsync version 0.5} + +\vspace*{5mm} +\begin{tabular}{|l|l|l|l|l|l|l|} \hline +{\bf block} & {\bf matches} & {\bf tag} & {\bf false} & {\bf data} & {\bf written} & {\bf read} \\ +{\bf size} & & {\bf hits} & {\bf alarms} & & & \\ \hline \hline + +300 & 64247 & 3817434 & 948 & 5312200 & 5629158 & 1632284 \\ \hline +500 & 46989 & 620013 & 64 & 1091900 & 1283906 & 979384 \\ \hline +700 & 33255 & 571970 & 22 & 1307800 & 1444346 & 699564 \\ \hline +900 & 25686 & 525058 & 24 & 1469500 & 1575438 & 544124 \\ \hline +1100 & 20848 & 496844 & 21 & 1654500 & 1740838 & 445204 \\ \hline +\end{tabular} +\vspace*{5mm} + +In each case, the CPU time taken was less than the +time it takes to run ``diff'' on the two files.\footnote{The wall + clock time was approximately 2 minutes per run on a 50 MHz SPARC 10 + running SunOS, using rsh over loopback for communication. GNU diff + took about 4 minutes.} + +The columns in the table are as follows: + +\begin{description} +\item [block size] The size in bytes of the checksummed blocks. +\item [matches] The number of times a block of $B$ was found in $A$. +\item [tag hits] The number of times the 16-bit hash of the rolling + checksum matched a hash of one of the checksums from $B$. +\item [false alarms] The number of times the 32-bit rolling checksum + matched but the strong checksum didn't. +\item [data] The amount of file data transferred verbatim, in bytes. +\item [written] The total number of bytes written by $\alpha$, + including protocol overheads. This is almost all file data. +\item [read] The total number of bytes read by $\alpha$, including + protocol overheads. This is almost all checksum information. +\end{description} + +The results demonstrate that for block sizes above 300 bytes, only a +small fraction (around 5\%) of the file was transferred. The amount +transferred was also considerably less than the size of the diff file +that would have been transferred if the diff/patch method of updating +a remote file was used. + +The checksums themselves took up a considerable amount of space, +although much less than the size of the data transferred in each +case. Each pair of checksums consumes 20 bytes: 4 bytes for the +rolling checksum plus 16 bytes for the 128-bit MD4 checksum. + +The number of false alarms was less than $1/1000$ of the number of +true matches, indicating that the 32-bit rolling checksum is quite +good at screening out false matches. + +The number of tag hits indicates that the second level of the +checksum search algorithm was invoked about once every 50 +characters. This is quite high because the total number of blocks in +the file is a large fraction of the size of the tag hash table. For +smaller files we would expect the tag hit rate to be much closer to +the number of matches. For extremely large files, we should probably +increase the size of the hash table. + +The next table shows similar results for a much smaller set of files. +In this case the files were not packed into a tar file first. Rather, +rsync was invoked with an option to recursively descend the directory +tree. The files used were from two source releases of another software +package called Samba. The total source code size is 1.7 MB and the +diff between the two releases is 4155 lines long totalling 120 kB. + +\vspace*{5mm} +\begin{tabular}{|l|l|l|l|l|l|l|} \hline +{\bf block} & {\bf matches} & {\bf tag} & {\bf false} & {\bf data} & {\bf written} & {\bf read} \\ +{\bf size} & & {\bf hits} & {\bf alarms} & & & \\ \hline \hline + +300 & 3727 & 3899 & 0 & 129775 & 153999 & 83948 \\ \hline +500 & 2158 & 2325 & 0 & 171574 & 189330 & 50908 \\ \hline +700 & 1517 & 1649 & 0 & 195024 & 210144 & 36828 \\ \hline +900 & 1156 & 1281 & 0 & 222847 & 236471 & 29048 \\ \hline +1100 & 921 & 1049 & 0 & 250073 & 262725 & 23988 \\ \hline +\end{tabular} +\vspace*{5mm} + + +\section{Availability} + +An implementation of rsync which provides a convenient interface +similar to the common UNIX command rcp has been written and is +available for download from http://rsync.samba.org/ + +\end{document} diff --git a/rsync/testhelp/maketree.py b/rsync/testhelp/maketree.py new file mode 100644 index 0000000..d71779a --- /dev/null +++ b/rsync/testhelp/maketree.py @@ -0,0 +1,133 @@ +#! /usr/bin/python2.2 + +# Copyright (C) 2002 by Martin Pool + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version +# 2 as published by the Free Software Foundation. +# +# 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +# Populate a tree with pseudo-randomly distributed files to test +# rsync. + +from __future__ import generators +import random, string, os, os.path + +nfiles = 10000 +depth = 5 +n_children = 20 +n_files = 20 +n_symlinks = 10 + +name_chars = string.digits + string.letters + +abuffer = 'a' * 1024 + +def random_name_chars(): + a = "" + for i in range(10): + a = a + random.choice(name_chars) + return a + + +def generate_names(): + n = 0 + while 1: + yield "%05d_%s" % (n, random_name_chars()) + n += 1 + + +class TreeBuilder: + def __init__(self): + self.n_children = 20 + self.n_files = 100 + self.total_entries = 100000 # long(1e8) + self.actual_size = 0 + self.name_gen = generate_names() + self.all_files = [] + self.all_dirs = [] + self.all_symlinks = [] + + + def random_size(self): + return random.lognormvariate(4, 4) + + + def random_symlink_target(self): + what = random.choice(['directory', 'file', 'symlink', 'none']) + try: + if what == 'directory': + return random.choice(self.all_dirs) + elif what == 'file': + return random.choice(self.all_files) + elif what == 'symlink': + return random.choice(self.all_symlinks) + elif what == 'none': + return self.name_gen.next() + except IndexError: + return self.name_gen.next() + + + def can_continue(self): + self.total_entries -= 1 + return self.total_entries > 0 + + + def build_tree(self, prefix, depth): + """Generate a breadth-first tree""" + for count, function in [[n_files, self.make_file], + [n_children, self.make_child_recurse], + [n_symlinks, self.make_symlink]]: + for i in range(count): + if not self.can_continue(): + return + name = os.path.join(prefix, self.name_gen.next()) + function(name, depth) + + + def print_summary(self): + print "total bytes: %d" % self.actual_size + + + def make_child_recurse(self, dname, depth): + if depth > 1: + self.make_dir(dname) + self.build_tree(dname, depth-1) + + + def make_dir(self, dname, depth='ignore'): + print "%s/" % (dname) + os.mkdir(dname) + self.all_dirs.append(dname) + + + def make_symlink(self, lname, depth='ignore'): + print "%s -> %s" % (lname, self.random_symlink_target()) + + + def make_file(self, fname, depth='ignore'): + size = long(self.random_size()) + print "%-70s %d" % (fname, size) + f = open(fname, 'w') + f.truncate(size) + self.fill_file(f, size) + self.all_files.append(fname) + self.actual_size += size + + def fill_file(self, f, size): + while size > 0: + f.write(abuffer[:size]) + size -= len(abuffer) + + +tb = TreeBuilder() +tb.build_tree('/tmp/foo', 3) +tb.print_summary() diff --git a/rsync/testrun.c b/rsync/testrun.c new file mode 100644 index 0000000..049e3eb --- /dev/null +++ b/rsync/testrun.c @@ -0,0 +1,61 @@ +/* Run a testsuite script with a timeout. */ + +#include "rsync.h" + +#define DEFAULT_TIMEOUT_SECS (5*60) +#define TIMEOUT_ENV "TESTRUN_TIMEOUT" + + int main(int argc, char *argv[]) +{ + pid_t pid; + char *timeout_env; + int status, timeout_secs, slept = 0; + + if (argc < 2) { + fprintf(stderr, "Usage: testrun [SHELL_OPTIONS] TESTSUITE_SCRIPT [ARGS]\n"); + exit(1); + } + + if ((timeout_env = getenv(TIMEOUT_ENV)) != NULL) + timeout_secs = atoi(timeout_env); + else + timeout_secs = DEFAULT_TIMEOUT_SECS; + + if ((pid = fork()) < 0) { + fprintf(stderr, "TESTRUN ERROR: fork failed: %s\n", strerror(errno)); + exit(1); + } + + if (pid == 0) { + argv[0] = "sh"; + execvp(argv[0], argv); + fprintf(stderr, "TESTRUN ERROR: failed to exec %s: %s\n", argv[0], strerror(errno)); + _exit(1); + } + + while (1) { + int ret = waitpid(pid, &status, WNOHANG); + if (ret > 0) + break; + if (ret < 0) { + if (errno == EINTR) + continue; + fprintf(stderr, "TESTRUN ERROR: waitpid failed: %s\n", strerror(errno)); + exit(1); + } + if (slept++ > timeout_secs) { + fprintf(stderr, "TESTRUN TIMEOUT: test took over %d seconds.\n", timeout_secs); + if (kill(pid, SIGTERM) < 0) + fprintf(stderr, "TESTRUN ERROR: failed to kill pid %d: %s\n", (int)pid, strerror(errno)); + else + fprintf(stderr, "TESTRUN INFO: killed pid %d\n", (int)pid); + exit(1); + } + sleep(1); + } + + if (!WIFEXITED(status)) + exit(255); + + return WEXITSTATUS(status); +} diff --git a/rsync/tls.c b/rsync/tls.c new file mode 100644 index 0000000..2197d16 --- /dev/null +++ b/rsync/tls.c @@ -0,0 +1,261 @@ +/* + * Trivial ls for comparing two directories after running an rsync. + * + * Copyright (C) 2001, 2002 Martin Pool + * Copyright (C) 2003-2014 Wayne Davison + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/* The problem with using the system's own ls is that some features + * have little quirks that make directories look different when for + * our purposes they're the same -- for example, the BSD braindamage + * about setting the mode on symlinks based on your current umask. + * + * All the filenames must be given on the command line -- tls does not + * even read directories, let alone recurse. The typical usage is + * "find|sort|xargs tls". + * + * The format is not exactly the same as any particular Unix ls(1). + * + * A key requirement for this program is that the output be "very + * reproducible." So we mask away information that can accidentally + * change. */ + +#include "rsync.h" +#include +#include "lib/sysxattrs.h" + +#define PROGRAM "tls" + +/* These are to make syscall.o shut up. */ +int dry_run = 0; +int am_root = 0; +int am_sender = 1; +int read_only = 1; +int list_only = 0; +int link_times = 0; +int link_owner = 0; +int nsec_times = 0; +int preserve_perms = 0; +int preserve_executability = 0; + +#ifdef SUPPORT_XATTRS + +#ifdef HAVE_LINUX_XATTRS +#define XSTAT_ATTR "user.rsync.%stat" +#else +#define XSTAT_ATTR "rsync.%stat" +#endif + +static int stat_xattr(const char *fname, STRUCT_STAT *fst) +{ + int mode, rdev_major, rdev_minor, uid, gid, len; + char buf[256]; + + if (am_root >= 0 || IS_DEVICE(fst->st_mode) || IS_SPECIAL(fst->st_mode)) + return -1; + + len = sys_lgetxattr(fname, XSTAT_ATTR, buf, sizeof buf - 1); + if (len >= (int)sizeof buf) { + len = -1; + errno = ERANGE; + } + if (len < 0) { + if (errno == ENOTSUP || errno == ENOATTR) + return -1; + if (errno == EPERM && S_ISLNK(fst->st_mode)) { + fst->st_uid = 0; + fst->st_gid = 0; + return 0; + } + fprintf(stderr, "failed to read xattr %s for %s: %s\n", + XSTAT_ATTR, fname, strerror(errno)); + return -1; + } + buf[len] = '\0'; + + if (sscanf(buf, "%o %d,%d %d:%d", + &mode, &rdev_major, &rdev_minor, &uid, &gid) != 5) { + fprintf(stderr, "Corrupt %s xattr attached to %s: \"%s\"\n", + XSTAT_ATTR, fname, buf); + exit(1); + } + +#if _S_IFLNK != 0120000 + if ((mode & (_S_IFMT)) == 0120000) + mode = (mode & ~(_S_IFMT)) | _S_IFLNK; +#endif + fst->st_mode = mode; + + fst->st_rdev = MAKEDEV(rdev_major, rdev_minor); + fst->st_uid = uid; + fst->st_gid = gid; + + return 0; +} + +#endif + +static void failed(char const *what, char const *where) +{ + fprintf(stderr, PROGRAM ": %s %s: %s\n", + what, where, strerror(errno)); + exit(1); +} + +static void list_file(const char *fname) +{ + STRUCT_STAT buf; + char permbuf[PERMSTRING_SIZE]; + struct tm *mt; + char datebuf[50]; + char linkbuf[4096]; + + if (do_lstat(fname, &buf) < 0) + failed("stat", fname); +#ifdef SUPPORT_XATTRS + if (am_root < 0) + stat_xattr(fname, &buf); +#endif + + /* The size of anything but a regular file is probably not + * worth thinking about. */ + if (!S_ISREG(buf.st_mode)) + buf.st_size = 0; + + /* On some BSD platforms the mode bits of a symlink are + * undefined. Also it tends not to be possible to reset a + * symlink's mtime, so we default to ignoring it too. */ + if (S_ISLNK(buf.st_mode)) { + int len; + buf.st_mode &= ~0777; + if (!link_times) + buf.st_mtime = (time_t)0; + if (!link_owner) + buf.st_uid = buf.st_gid = 0; + strlcpy(linkbuf, " -> ", sizeof linkbuf); + /* const-cast required for silly UNICOS headers */ + len = do_readlink((char *) fname, linkbuf+4, sizeof(linkbuf) - 4); + if (len == -1) + failed("do_readlink", fname); + else + /* it's not nul-terminated */ + linkbuf[4+len] = 0; + } else { + linkbuf[0] = 0; + } + + permstring(permbuf, buf.st_mode); + + if (buf.st_mtime) { + int len; + mt = gmtime(&buf.st_mtime); + + len = snprintf(datebuf, sizeof datebuf, + "%04d-%02d-%02d %02d:%02d:%02d", + (int)mt->tm_year + 1900, + (int)mt->tm_mon + 1, + (int)mt->tm_mday, + (int)mt->tm_hour, + (int)mt->tm_min, + (int)mt->tm_sec); +#ifdef ST_MTIME_NSEC + if (nsec_times) { + snprintf(datebuf + len, sizeof datebuf - len, + ".%09d", (int)buf.ST_MTIME_NSEC); + } +#endif + } else { + int len = MIN(19 + 9*nsec_times, (int)sizeof datebuf - 1); + memset(datebuf, ' ', len); + datebuf[len] = '\0'; + } + + /* TODO: Perhaps escape special characters in fname? */ + + printf("%s ", permbuf); + if (S_ISCHR(buf.st_mode) || S_ISBLK(buf.st_mode)) { + printf("%5ld,%6ld", + (long)major(buf.st_rdev), + (long)minor(buf.st_rdev)); + } else + printf("%15s", do_big_num(buf.st_size, 1, NULL)); + printf(" %6ld.%-6ld %6ld %s %s%s\n", + (long)buf.st_uid, (long)buf.st_gid, (long)buf.st_nlink, + datebuf, fname, linkbuf); +} + +static struct poptOption long_options[] = { + /* longName, shortName, argInfo, argPtr, value, descrip, argDesc */ + {"link-times", 'l', POPT_ARG_NONE, &link_times, 0, 0, 0 }, + {"link-owner", 'L', POPT_ARG_NONE, &link_owner, 0, 0, 0 }, +#ifdef SUPPORT_XATTRS + {"fake-super", 'f', POPT_ARG_VAL, &am_root, -1, 0, 0 }, +#endif +#ifdef ST_MTIME_NSEC + {"nsec", 's', POPT_ARG_NONE, &nsec_times, 0, 0, 0 }, +#endif + {"help", 'h', POPT_ARG_NONE, 0, 'h', 0, 0 }, + {0,0,0,0,0,0,0} +}; + +static void tls_usage(int ret) +{ + FILE *F = ret ? stderr : stdout; + fprintf(F,"usage: " PROGRAM " [OPTIONS] FILE ...\n"); + fprintf(F,"Trivial file listing program for portably checking rsync\n"); + fprintf(F,"\nOptions:\n"); + fprintf(F," -l, --link-times display the time on a symlink\n"); + fprintf(F," -L, --link-owner display the owner+group on a symlink\n"); +#ifdef SUPPORT_XATTRS + fprintf(F," -f, --fake-super display attributes including fake-super xattrs\n"); +#endif + fprintf(F," -h, --help show this help\n"); + exit(ret); +} + +int +main(int argc, char *argv[]) +{ + poptContext pc; + const char **extra_args; + int opt; + + pc = poptGetContext(PROGRAM, argc, (const char **)argv, + long_options, 0); + while ((opt = poptGetNextOpt(pc)) != -1) { + switch (opt) { + case 'h': + tls_usage(0); + default: + fprintf(stderr, + "%s: %s\n", + poptBadOption(pc, POPT_BADOPTION_NOALIAS), + poptStrerror(opt)); + tls_usage(1); + } + } + + extra_args = poptGetArgs(pc); + if (!extra_args || *extra_args == NULL) + tls_usage(1); + + for (; *extra_args; extra_args++) + list_file(*extra_args); + poptFreeContext(pc); + + return 0; +} diff --git a/rsync/token.c b/rsync/token.c new file mode 100644 index 0000000..8cc5532 --- /dev/null +++ b/rsync/token.c @@ -0,0 +1,666 @@ +/* + * Routines used by the file-transfer code. + * + * Copyright (C) 1996 Andrew Tridgell + * Copyright (C) 1996 Paul Mackerras + * Copyright (C) 2003-2014 Wayne Davison + * + * 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, visit the http://fsf.org website. + */ + +#include "rsync.h" +#include "itypes.h" +#include + +extern int do_compression; +extern int protocol_version; +extern int module_id; +extern int def_compress_level; +extern char *skip_compress; + +static int compression_level, per_file_default_level; + +struct suffix_tree { + struct suffix_tree *sibling; + struct suffix_tree *child; + char letter, word_end; +}; + +static char *match_list; +static struct suffix_tree *suftree; + +static void add_suffix(struct suffix_tree **prior, char ltr, const char *str) +{ + struct suffix_tree *node, *newnode; + + if (ltr == '[') { + const char *after = strchr(str, ']'); + /* Treat "[foo" and "[]" as having a literal '['. */ + if (after && after++ != str+1) { + while ((ltr = *str++) != ']') + add_suffix(prior, ltr, after); + return; + } + } + + for (node = *prior; node; prior = &node->sibling, node = node->sibling) { + if (node->letter == ltr) { + if (*str) + add_suffix(&node->child, *str, str+1); + else + node->word_end = 1; + return; + } + if (node->letter > ltr) + break; + } + if (!(newnode = new(struct suffix_tree))) + out_of_memory("add_suffix"); + newnode->sibling = node; + newnode->child = NULL; + newnode->letter = ltr; + *prior = newnode; + if (*str) { + add_suffix(&newnode->child, *str, str+1); + newnode->word_end = 0; + } else + newnode->word_end = 1; +} + +static void add_nocompress_suffixes(const char *str) +{ + char *buf, *t; + const char *f = str; + + if (!(buf = new_array(char, strlen(f) + 1))) + out_of_memory("add_nocompress_suffixes"); + + while (*f) { + if (*f == '/') { + f++; + continue; + } + + t = buf; + do { + if (isUpper(f)) + *t++ = toLower(f); + else + *t++ = *f; + } while (*++f != '/' && *f); + *t++ = '\0'; + + add_suffix(&suftree, *buf, buf+1); + } + + free(buf); +} + +static void init_set_compression(void) +{ + const char *f; + char *t, *start; + + if (skip_compress) + add_nocompress_suffixes(skip_compress); + + /* A non-daemon transfer skips the default suffix list if the + * user specified --skip-compress. */ + if (skip_compress && module_id < 0) + f = ""; + else + f = lp_dont_compress(module_id); + + if (!(match_list = t = new_array(char, strlen(f) + 2))) + out_of_memory("set_compression"); + + per_file_default_level = def_compress_level; + + while (*f) { + if (*f == ' ') { + f++; + continue; + } + + start = t; + do { + if (isUpper(f)) + *t++ = toLower(f); + else + *t++ = *f; + } while (*++f != ' ' && *f); + *t++ = '\0'; + + if (t - start == 1+1 && *start == '*') { + /* Optimize a match-string of "*". */ + *match_list = '\0'; + suftree = NULL; + per_file_default_level = 0; + break; + } + + /* Move *.foo items into the stuffix tree. */ + if (*start == '*' && start[1] == '.' && start[2] + && !strpbrk(start+2, ".?*")) { + add_suffix(&suftree, start[2], start+3); + t = start; + } + } + *t++ = '\0'; +} + +/* determine the compression level based on a wildcard filename list */ +void set_compression(const char *fname) +{ + const struct suffix_tree *node; + const char *s; + char ltr; + + if (!do_compression) + return; + + if (!match_list) + init_set_compression(); + + compression_level = per_file_default_level; + + if (!*match_list && !suftree) + return; + + if ((s = strrchr(fname, '/')) != NULL) + fname = s + 1; + + for (s = match_list; *s; s += strlen(s) + 1) { + if (iwildmatch(s, fname)) { + compression_level = 0; + return; + } + } + + if (!(node = suftree) || !(s = strrchr(fname, '.')) + || s == fname || !(ltr = *++s)) + return; + + while (1) { + if (isUpper(<r)) + ltr = toLower(<r); + while (node->letter != ltr) { + if (node->letter > ltr) + return; + if (!(node = node->sibling)) + return; + } + if ((ltr = *++s) == '\0') { + if (node->word_end) + compression_level = 0; + return; + } + if (!(node = node->child)) + return; + } +} + +/* non-compressing recv token */ +static int32 simple_recv_token(int f, char **data) +{ + static int32 residue; + static char *buf; + int32 n; + + if (!buf) { + buf = new_array(char, CHUNK_SIZE); + if (!buf) + out_of_memory("simple_recv_token"); + } + + if (residue == 0) { + int32 i = read_int(f); + if (i <= 0) + return i; + residue = i; + } + + *data = buf; + n = MIN(CHUNK_SIZE,residue); + residue -= n; + read_buf(f,buf,n); + return n; +} + +/* non-compressing send token */ +static void simple_send_token(int f, int32 token, struct map_struct *buf, + OFF_T offset, int32 n) +{ + if (n > 0) { + int32 len = 0; + while (len < n) { + int32 n1 = MIN(CHUNK_SIZE, n-len); + write_int(f, n1); + write_buf(f, map_ptr(buf, offset+len, n1), n1); + len += n1; + } + } + /* a -2 token means to send data only and no token */ + if (token != -2) + write_int(f, -(token+1)); +} + +/* Flag bytes in compressed stream are encoded as follows: */ +#define END_FLAG 0 /* that's all folks */ +#define TOKEN_LONG 0x20 /* followed by 32-bit token number */ +#define TOKENRUN_LONG 0x21 /* ditto with 16-bit run count */ +#define DEFLATED_DATA 0x40 /* + 6-bit high len, then low len byte */ +#define TOKEN_REL 0x80 /* + 6-bit relative token number */ +#define TOKENRUN_REL 0xc0 /* ditto with 16-bit run count */ + +#define MAX_DATA_COUNT 16383 /* fit 14 bit count into 2 bytes with flags */ + +/* zlib.h says that if we want to be able to compress something in a single + * call, avail_out must be at least 0.1% larger than avail_in plus 12 bytes. + * We'll add in 0.1%+16, just to be safe (and we'll avoid floating point, + * to ensure that this is a compile-time value). */ +#define AVAIL_OUT_SIZE(avail_in_size) ((avail_in_size)*1001/1000+16) + +/* For coding runs of tokens */ +static int32 last_token = -1; +static int32 run_start; +static int32 last_run_end; + +/* Deflation state */ +static z_stream tx_strm; + +/* Output buffer */ +static char *obuf; + +/* We want obuf to be able to hold both MAX_DATA_COUNT+2 bytes as well as + * AVAIL_OUT_SIZE(CHUNK_SIZE) bytes, so make sure that it's large enough. */ +#if MAX_DATA_COUNT+2 > AVAIL_OUT_SIZE(CHUNK_SIZE) +#define OBUF_SIZE (MAX_DATA_COUNT+2) +#else +#define OBUF_SIZE AVAIL_OUT_SIZE(CHUNK_SIZE) +#endif + +/* Send a deflated token */ +static void +send_deflated_token(int f, int32 token, struct map_struct *buf, OFF_T offset, + int32 nb, int32 toklen) +{ + int32 n, r; + static int init_done, flush_pending; + + if (last_token == -1) { + /* initialization */ + if (!init_done) { + tx_strm.next_in = NULL; + tx_strm.zalloc = NULL; + tx_strm.zfree = NULL; + if (deflateInit2(&tx_strm, compression_level, + Z_DEFLATED, -15, 8, + Z_DEFAULT_STRATEGY) != Z_OK) { + rprintf(FERROR, "compression init failed\n"); + exit_cleanup(RERR_PROTOCOL); + } + if ((obuf = new_array(char, OBUF_SIZE)) == NULL) + out_of_memory("send_deflated_token"); + init_done = 1; + } else + deflateReset(&tx_strm); + last_run_end = 0; + run_start = token; + flush_pending = 0; + } else if (last_token == -2) { + run_start = token; + } else if (nb != 0 || token != last_token + 1 + || token >= run_start + 65536) { + /* output previous run */ + r = run_start - last_run_end; + n = last_token - run_start; + if (r >= 0 && r <= 63) { + write_byte(f, (n==0? TOKEN_REL: TOKENRUN_REL) + r); + } else { + write_byte(f, (n==0? TOKEN_LONG: TOKENRUN_LONG)); + write_int(f, run_start); + } + if (n != 0) { + write_byte(f, n); + write_byte(f, n >> 8); + } + last_run_end = last_token; + run_start = token; + } + + last_token = token; + + if (nb != 0 || flush_pending) { + /* deflate the data starting at offset */ + int flush = Z_NO_FLUSH; + tx_strm.avail_in = 0; + tx_strm.avail_out = 0; + do { + if (tx_strm.avail_in == 0 && nb != 0) { + /* give it some more input */ + n = MIN(nb, CHUNK_SIZE); + tx_strm.next_in = (Bytef *) + map_ptr(buf, offset, n); + tx_strm.avail_in = n; + nb -= n; + offset += n; + } + if (tx_strm.avail_out == 0) { + tx_strm.next_out = (Bytef *)(obuf + 2); + tx_strm.avail_out = MAX_DATA_COUNT; + if (flush != Z_NO_FLUSH) { + /* + * We left the last 4 bytes in the + * buffer, in case they are the + * last 4. Move them to the front. + */ + memcpy(tx_strm.next_out, + obuf+MAX_DATA_COUNT-2, 4); + tx_strm.next_out += 4; + tx_strm.avail_out -= 4; + } + } + if (nb == 0 && token != -2) + flush = Z_SYNC_FLUSH; + r = deflate(&tx_strm, flush); + if (r != Z_OK) { + rprintf(FERROR, "deflate returned %d\n", r); + exit_cleanup(RERR_STREAMIO); + } + if (nb == 0 || tx_strm.avail_out == 0) { + n = MAX_DATA_COUNT - tx_strm.avail_out; + if (flush != Z_NO_FLUSH) { + /* + * We have to trim off the last 4 + * bytes of output when flushing + * (they are just 0, 0, ff, ff). + */ + n -= 4; + } + if (n > 0) { + obuf[0] = DEFLATED_DATA + (n >> 8); + obuf[1] = n; + write_buf(f, obuf, n+2); + } + } + } while (nb != 0 || tx_strm.avail_out == 0); + flush_pending = token == -2; + } + + if (token == -1) { + /* end of file - clean up */ + write_byte(f, END_FLAG); + } else if (token != -2 && do_compression == 1) { + /* Add the data in the current block to the compressor's + * history and hash table. */ +#ifndef EXTERNAL_ZLIB + do { + /* Break up long sections in the same way that + * see_deflate_token() does. */ + int32 n1 = toklen > 0xffff ? 0xffff : toklen; + toklen -= n1; + tx_strm.next_in = (Bytef *)map_ptr(buf, offset, n1); + tx_strm.avail_in = n1; + if (protocol_version >= 31) /* Newer protocols avoid a data-duplicating bug */ + offset += n1; + tx_strm.next_out = (Bytef *) obuf; + tx_strm.avail_out = AVAIL_OUT_SIZE(CHUNK_SIZE); + r = deflate(&tx_strm, Z_INSERT_ONLY); + if (r != Z_OK || tx_strm.avail_in != 0) { + rprintf(FERROR, "deflate on token returned %d (%d bytes left)\n", + r, tx_strm.avail_in); + exit_cleanup(RERR_STREAMIO); + } + } while (toklen > 0); +#else + toklen++; + rprintf(FERROR, "Impossible error in external-zlib code (1).\n"); + exit_cleanup(RERR_STREAMIO); +#endif + } +} + +/* tells us what the receiver is in the middle of doing */ +static enum { r_init, r_idle, r_running, r_inflating, r_inflated } recv_state; + +/* for inflating stuff */ +static z_stream rx_strm; +static char *cbuf; +static char *dbuf; + +/* for decoding runs of tokens */ +static int32 rx_token; +static int32 rx_run; + +/* Receive a deflated token and inflate it */ +static int32 recv_deflated_token(int f, char **data) +{ + static int init_done; + static int32 saved_flag; + int32 n, flag; + int r; + + for (;;) { + switch (recv_state) { + case r_init: + if (!init_done) { + rx_strm.next_out = NULL; + rx_strm.zalloc = NULL; + rx_strm.zfree = NULL; + if (inflateInit2(&rx_strm, -15) != Z_OK) { + rprintf(FERROR, "inflate init failed\n"); + exit_cleanup(RERR_PROTOCOL); + } + if (!(cbuf = new_array(char, MAX_DATA_COUNT)) + || !(dbuf = new_array(char, AVAIL_OUT_SIZE(CHUNK_SIZE)))) + out_of_memory("recv_deflated_token"); + init_done = 1; + } else { + inflateReset(&rx_strm); + } + recv_state = r_idle; + rx_token = 0; + break; + + case r_idle: + case r_inflated: + if (saved_flag) { + flag = saved_flag & 0xff; + saved_flag = 0; + } else + flag = read_byte(f); + if ((flag & 0xC0) == DEFLATED_DATA) { + n = ((flag & 0x3f) << 8) + read_byte(f); + read_buf(f, cbuf, n); + rx_strm.next_in = (Bytef *)cbuf; + rx_strm.avail_in = n; + recv_state = r_inflating; + break; + } + if (recv_state == r_inflated) { + /* check previous inflated stuff ended correctly */ + rx_strm.avail_in = 0; + rx_strm.next_out = (Bytef *)dbuf; + rx_strm.avail_out = AVAIL_OUT_SIZE(CHUNK_SIZE); + r = inflate(&rx_strm, Z_SYNC_FLUSH); + n = AVAIL_OUT_SIZE(CHUNK_SIZE) - rx_strm.avail_out; + /* + * Z_BUF_ERROR just means no progress was + * made, i.e. the decompressor didn't have + * any pending output for us. + */ + if (r != Z_OK && r != Z_BUF_ERROR) { + rprintf(FERROR, "inflate flush returned %d (%d bytes)\n", + r, n); + exit_cleanup(RERR_STREAMIO); + } + if (n != 0 && r != Z_BUF_ERROR) { + /* have to return some more data and + save the flag for later. */ + saved_flag = flag + 0x10000; + *data = dbuf; + return n; + } + /* + * At this point the decompressor should + * be expecting to see the 0, 0, ff, ff bytes. + */ + if (!inflateSyncPoint(&rx_strm)) { + rprintf(FERROR, "decompressor lost sync!\n"); + exit_cleanup(RERR_STREAMIO); + } + rx_strm.avail_in = 4; + rx_strm.next_in = (Bytef *)cbuf; + cbuf[0] = cbuf[1] = 0; + cbuf[2] = cbuf[3] = 0xff; + inflate(&rx_strm, Z_SYNC_FLUSH); + recv_state = r_idle; + } + if (flag == END_FLAG) { + /* that's all folks */ + recv_state = r_init; + return 0; + } + + /* here we have a token of some kind */ + if (flag & TOKEN_REL) { + rx_token += flag & 0x3f; + flag >>= 6; + } else + rx_token = read_int(f); + if (flag & 1) { + rx_run = read_byte(f); + rx_run += read_byte(f) << 8; + recv_state = r_running; + } + return -1 - rx_token; + + case r_inflating: + rx_strm.next_out = (Bytef *)dbuf; + rx_strm.avail_out = AVAIL_OUT_SIZE(CHUNK_SIZE); + r = inflate(&rx_strm, Z_NO_FLUSH); + n = AVAIL_OUT_SIZE(CHUNK_SIZE) - rx_strm.avail_out; + if (r != Z_OK) { + rprintf(FERROR, "inflate returned %d (%d bytes)\n", r, n); + exit_cleanup(RERR_STREAMIO); + } + if (rx_strm.avail_in == 0) + recv_state = r_inflated; + if (n != 0) { + *data = dbuf; + return n; + } + break; + + case r_running: + ++rx_token; + if (--rx_run == 0) + recv_state = r_idle; + return -1 - rx_token; + } + } +} + +/* + * put the data corresponding to a token that we've just returned + * from recv_deflated_token into the decompressor's history buffer. + */ +static void see_deflate_token(char *buf, int32 len) +{ +#ifndef EXTERNAL_ZLIB + int r; + int32 blklen; + unsigned char hdr[5]; + + rx_strm.avail_in = 0; + blklen = 0; + hdr[0] = 0; + do { + if (rx_strm.avail_in == 0 && len != 0) { + if (blklen == 0) { + /* Give it a fake stored-block header. */ + rx_strm.next_in = (Bytef *)hdr; + rx_strm.avail_in = 5; + blklen = len; + if (blklen > 0xffff) + blklen = 0xffff; + hdr[1] = blklen; + hdr[2] = blklen >> 8; + hdr[3] = ~hdr[1]; + hdr[4] = ~hdr[2]; + } else { + rx_strm.next_in = (Bytef *)buf; + rx_strm.avail_in = blklen; + if (protocol_version >= 31) /* Newer protocols avoid a data-duplicating bug */ + buf += blklen; + len -= blklen; + blklen = 0; + } + } + rx_strm.next_out = (Bytef *)dbuf; + rx_strm.avail_out = AVAIL_OUT_SIZE(CHUNK_SIZE); + r = inflate(&rx_strm, Z_SYNC_FLUSH); + if (r != Z_OK && r != Z_BUF_ERROR) { + rprintf(FERROR, "inflate (token) returned %d\n", r); + exit_cleanup(RERR_STREAMIO); + } + } while (len || rx_strm.avail_out == 0); +#else + buf++; len++; + rprintf(FERROR, "Impossible error in external-zlib code (2).\n"); + exit_cleanup(RERR_STREAMIO); +#endif +} + +/** + * Transmit a verbatim buffer of length @p n followed by a token. + * If token == -1 then we have reached EOF + * If n == 0 then don't send a buffer + */ +void send_token(int f, int32 token, struct map_struct *buf, OFF_T offset, + int32 n, int32 toklen) +{ + if (!do_compression) + simple_send_token(f, token, buf, offset, n); + else + send_deflated_token(f, token, buf, offset, n, toklen); +} + +/* + * receive a token or buffer from the other end. If the reurn value is >0 then + * it is a data buffer of that length, and *data will point at the data. + * if the return value is -i then it represents token i-1 + * if the return value is 0 then the end has been reached + */ +int32 recv_token(int f, char **data) +{ + int tok; + + if (!do_compression) { + tok = simple_recv_token(f,data); + } else { + tok = recv_deflated_token(f, data); + } + return tok; +} + +/* + * look at the data corresponding to a token, if necessary + */ +void see_token(char *data, int32 toklen) +{ + if (do_compression == 1) + see_deflate_token(data, toklen); +} diff --git a/rsync/trimslash.c b/rsync/trimslash.c new file mode 100644 index 0000000..8ee4766 --- /dev/null +++ b/rsync/trimslash.c @@ -0,0 +1,47 @@ +/* + * Simple utility used only by the test harness. + * + * Copyright (C) 2002 Martin Pool + * Copyright (C) 2003-2014 Wayne Davison + * + * 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, visit the http://fsf.org website. + */ + +#include "rsync.h" + +/* These are to make syscall.o shut up. */ +int dry_run = 0; +int am_root = 0; +int am_sender = 1; +int read_only = 1; +int list_only = 0; +int preserve_perms = 0; +int preserve_executability = 0; + +int +main(int argc, char **argv) +{ + int i; + + if (argc <= 1) { + fprintf(stderr, "trimslash: needs at least one argument\n"); + return 1; + } + + for (i = 1; i < argc; i++) { + trim_trailing_slashes(argv[i]); /* modify in place */ + printf("%s\n", argv[i]); + } + return 0; +} diff --git a/rsync/tweak_manpage b/rsync/tweak_manpage new file mode 100755 index 0000000..4c55c51 --- /dev/null +++ b/rsync/tweak_manpage @@ -0,0 +1,28 @@ +#!/usr/bin/perl -i -p + +use strict; +use warnings; + +# We only need to use "\&'" or "\&." at the start of a line. +s/(?<=.)\\\&(['.])/$1/g; + +# Some quotes turn into open/close quotes. +s/'(.)'/\\(oq$1\\(cq/g; +s/(^|[ (])"(?!( |$))/$1\\(lq/gm; +s/(?next = *root; + node->u = noiu; + node->id = id; + node->id2 = id2; + node->flags = flags; + *root = node; + return node; +} + +/* turn a uid into a user name */ +char *uid_to_user(uid_t uid) +{ + struct passwd *pass = getpwuid(uid); + if (pass) + return strdup(pass->pw_name); + return NULL; +} + +/* turn a gid into a group name */ +char *gid_to_group(gid_t gid) +{ + struct group *grp = getgrgid(gid); + if (grp) + return strdup(grp->gr_name); + return NULL; +} + +/* Parse a user name or (optionally) a number into a uid */ +int user_to_uid(const char *name, uid_t *uid_p, BOOL num_ok) +{ + struct passwd *pass; + if (!name || !*name) + return 0; + if (num_ok && name[strspn(name, "0123456789")] == '\0') { + *uid_p = id_parse(name); + return 1; + } + if (!(pass = getpwnam(name))) + return 0; + *uid_p = pass->pw_uid; + return 1; +} + +/* Parse a group name or (optionally) a number into a gid */ +int group_to_gid(const char *name, gid_t *gid_p, BOOL num_ok) +{ + struct group *grp; + if (!name || !*name) + return 0; + if (num_ok && name[strspn(name, "0123456789")] == '\0') { + *gid_p = id_parse(name); + return 1; + } + if (!(grp = getgrnam(name))) + return 0; + *gid_p = grp->gr_gid; + return 1; +} + +static int is_in_group(gid_t gid) +{ +#ifdef HAVE_GETGROUPS + static gid_t last_in; + static int ngroups = -2, last_out = -1; + static GETGROUPS_T *gidset; + int n; + + if (gid == last_in && last_out >= 0) + return last_out; + if (ngroups < -1) { + if ((ngroups = getgroups(0, NULL)) < 0) + ngroups = 0; + gidset = new_array(GETGROUPS_T, ngroups+1); + if (!gidset) + out_of_memory("is_in_group"); + if (ngroups > 0) + ngroups = getgroups(ngroups, gidset); + /* The default gid might not be in the list on some systems. */ + for (n = 0; n < ngroups; n++) { + if (gidset[n] == our_gid) + break; + } + if (n == ngroups) + gidset[ngroups++] = our_gid; + if (DEBUG_GTE(OWN, 2)) { + int pos; + char *gidbuf = new_array(char, ngroups*21+32); + if (!gidbuf) + out_of_memory("is_in_group"); + pos = snprintf(gidbuf, 32, "process has %d gid%s: ", + ngroups, ngroups == 1? "" : "s"); + for (n = 0; n < ngroups; n++) { + pos += snprintf(gidbuf+pos, 21, " %d", (int)gidset[n]); + } + rprintf(FINFO, "%s\n", gidbuf); + free(gidbuf); + } + } + + last_in = gid; + for (n = 0; n < ngroups; n++) { + if (gidset[n] == gid) + return last_out = 1; + } + return last_out = 0; + +#else + return gid == our_gid; +#endif +} + +/* Add a uid/gid to its list of ids. Only called on receiving side. */ +static struct idlist *recv_add_id(struct idlist **idlist_ptr, struct idlist *idmap, + id_t id, const char *name) +{ + struct idlist *node; + union name_or_id noiu; + int flag; + id_t id2; + + noiu.name = name; /* ensure that add_to_list() gets the raw value. */ + if (!name) + name = ""; + + for (node = idmap; node; node = node->next) { + if (node->flags & NFLAGS_WILD_NAME_MATCH) { + if (!wildmatch(node->u.name, name)) + continue; + } else if (node->flags & NFLAGS_NAME_MATCH) { + if (strcmp(node->u.name, name) != 0) + continue; + } else if (node->u.max_id) { + if (id < node->id || id > node->u.max_id) + continue; + } else { + if (node->id != id) + continue; + } + break; + } + if (node) + id2 = node->id2; + else if (*name && id) { + if (idlist_ptr == &uidlist) { + uid_t uid; + id2 = user_to_uid(name, &uid, False) ? uid : id; + } else { + gid_t gid; + id2 = group_to_gid(name, &gid, False) ? gid : id; + } + } else + id2 = id; + + flag = idlist_ptr == &gidlist && !am_root && !is_in_group(id2) ? FLAG_SKIP_GROUP : 0; + node = add_to_list(idlist_ptr, id, noiu, id2, flag); + + if (DEBUG_GTE(OWN, 2)) { + rprintf(FINFO, "%sid %u(%s) maps to %u\n", + idlist_ptr == &uidlist ? "u" : "g", + (unsigned)id, name, (unsigned)id2); + } + + return node; +} + +/* this function is a definate candidate for a faster algorithm */ +uid_t match_uid(uid_t uid) +{ + static struct idlist *last = NULL; + struct idlist *list; + + if (last && uid == last->id) + return last->id2; + + for (list = uidlist; list; list = list->next) { + if (list->id == uid) + break; + } + + if (!list) + list = recv_add_id(&uidlist, uidmap, uid, NULL); + last = list; + + return list->id2; +} + +gid_t match_gid(gid_t gid, uint16 *flags_ptr) +{ + static struct idlist *last = NULL; + struct idlist *list; + + if (last && gid == last->id) + list = last; + else { + for (list = gidlist; list; list = list->next) { + if (list->id == gid) + break; + } + if (!list) + list = recv_add_id(&gidlist, gidmap, gid, NULL); + last = list; + } + + if (flags_ptr && list->flags & FLAG_SKIP_GROUP) + *flags_ptr |= FLAG_SKIP_GROUP; + return list->id2; +} + +/* Add a uid to the list of uids. Only called on sending side. */ +const char *add_uid(uid_t uid) +{ + struct idlist *list; + struct idlist *node; + union name_or_id noiu; + + if (uid == 0) /* don't map root */ + return NULL; + + for (list = uidlist; list; list = list->next) { + if (list->id == uid) + return NULL; + } + + noiu.name = uid_to_user(uid); + node = add_to_list(&uidlist, uid, noiu, 0, 0); + return node->u.name; +} + +/* Add a gid to the list of gids. Only called on sending side. */ +const char *add_gid(gid_t gid) +{ + struct idlist *list; + struct idlist *node; + union name_or_id noiu; + + if (gid == 0) /* don't map root */ + return NULL; + + for (list = gidlist; list; list = list->next) { + if (list->id == gid) + return NULL; + } + + noiu.name = gid_to_group(gid); + node = add_to_list(&gidlist, gid, noiu, 0, 0); + return node->u.name; +} + +/* send a complete uid/gid mapping to the peer */ +void send_id_list(int f) +{ + struct idlist *list; + + if (preserve_uid || preserve_acls) { + int len; + /* we send sequences of uid/byte-length/name */ + for (list = uidlist; list; list = list->next) { + if (!list->u.name) + continue; + len = strlen(list->u.name); + write_varint30(f, list->id); + write_byte(f, len); + write_buf(f, list->u.name, len); + } + + /* terminate the uid list with a 0 uid. We explicitly exclude + * 0 from the list */ + write_varint30(f, 0); + } + + if (preserve_gid || preserve_acls) { + int len; + for (list = gidlist; list; list = list->next) { + if (!list->u.name) + continue; + len = strlen(list->u.name); + write_varint30(f, list->id); + write_byte(f, len); + write_buf(f, list->u.name, len); + } + write_varint30(f, 0); + } +} + +uid_t recv_user_name(int f, uid_t uid) +{ + struct idlist *node; + int len = read_byte(f); + char *name = new_array(char, len+1); + if (!name) + out_of_memory("recv_user_name"); + read_sbuf(f, name, len); + if (numeric_ids < 0) { + free(name); + name = NULL; + } + node = recv_add_id(&uidlist, uidmap, uid, name); /* node keeps name's memory */ + return node->id2; +} + +gid_t recv_group_name(int f, gid_t gid, uint16 *flags_ptr) +{ + struct idlist *node; + int len = read_byte(f); + char *name = new_array(char, len+1); + if (!name) + out_of_memory("recv_group_name"); + read_sbuf(f, name, len); + if (numeric_ids < 0) { + free(name); + name = NULL; + } + node = recv_add_id(&gidlist, gidmap, gid, name); /* node keeps name's memory */ + if (flags_ptr && node->flags & FLAG_SKIP_GROUP) + *flags_ptr |= FLAG_SKIP_GROUP; + return node->id2; +} + +/* recv a complete uid/gid mapping from the peer and map the uid/gid + * in the file list to local names */ +void recv_id_list(int f, struct file_list *flist) +{ + id_t id; + int i; + + if ((preserve_uid || preserve_acls) && numeric_ids <= 0) { + /* read the uid list */ + while ((id = read_varint30(f)) != 0) + recv_user_name(f, id); + } + + if ((preserve_gid || preserve_acls) && numeric_ids <= 0) { + /* read the gid list */ + while ((id = read_varint30(f)) != 0) + recv_group_name(f, id, NULL); + } + + /* Now convert all the uids/gids from sender values to our values. */ +#ifdef SUPPORT_ACLS + if (preserve_acls && (!numeric_ids || usermap || groupmap)) + match_acl_ids(); +#endif + if (am_root && preserve_uid && (!numeric_ids || usermap)) { + for (i = 0; i < flist->used; i++) + F_OWNER(flist->files[i]) = match_uid(F_OWNER(flist->files[i])); + } + if (preserve_gid && (!am_root || !numeric_ids || groupmap)) { + for (i = 0; i < flist->used; i++) { + F_GROUP(flist->files[i]) = match_gid(F_GROUP(flist->files[i]), + &flist->files[i]->flags); + } + } +} + +void parse_name_map(char *map, BOOL usernames) +{ + struct idlist **idmap_ptr = usernames ? &uidmap : &gidmap; + struct idlist **idlist_ptr = usernames ? &uidlist : &gidlist; + char *colon, *cp = map + strlen(map); + union name_or_id noiu; + id_t id1; + uint16 flags; + + /* Parse the list in reverse, so the order in the struct is right. */ + while (1) { + while (cp > map && cp[-1] != ',') cp--; + if (!(colon = strchr(cp, ':'))) { + rprintf(FERROR, "No colon found in --%smap: %s\n", + usernames ? "user" : "group", cp); + exit_cleanup(RERR_SYNTAX); + } + if (!colon[1]) { + rprintf(FERROR, "No name found after colon --%smap: %s\n", + usernames ? "user" : "group", cp); + exit_cleanup(RERR_SYNTAX); + } + *colon = '\0'; + + if (isDigit(cp)) { + char *dash = strchr(cp, '-'); + if (strspn(cp, "0123456789-") != (size_t)(colon - cp) + || (dash && (!dash[1] || strchr(dash+1, '-')))) { + rprintf(FERROR, "Invalid number in --%smap: %s\n", + usernames ? "user" : "group", cp); + exit_cleanup(RERR_SYNTAX); + } + if (dash) { + *dash = '\0'; + noiu.max_id = id_parse(dash+1); + } else + noiu.max_id = 0; + flags = 0; + id1 = id_parse(cp); + if (dash) + *dash = '-'; + } else if (strpbrk(cp, "*[?")) { + flags = NFLAGS_WILD_NAME_MATCH; + noiu.name = cp; + id1 = 0; + } else { + flags = NFLAGS_NAME_MATCH; + noiu.name = cp; + id1 = 0; + } + + if (usernames) { + uid_t uid; + if (user_to_uid(colon+1, &uid, True)) + add_to_list(idmap_ptr, id1, noiu, uid, flags); + else { + rprintf(FERROR, + "Unknown --usermap name on receiver: %s\n", + colon+1); + } + } else { + gid_t gid; + if (group_to_gid(colon+1, &gid, True)) + add_to_list(idmap_ptr, id1, noiu, gid, flags); + else { + rprintf(FERROR, + "Unknown --groupmap name on receiver: %s\n", + colon+1); + } + } + + if (cp == map) + break; + + *--cp = '\0'; /* replace comma */ + } + + /* The 0 user/group doesn't get its name sent, so add it explicitly. */ + recv_add_id(idlist_ptr, *idmap_ptr, 0, + numeric_ids ? NULL : usernames ? uid_to_user(0) : gid_to_group(0)); +} + +#ifdef HAVE_GETGROUPLIST +const char *getallgroups(uid_t uid, gid_t *gid_list, int *size_ptr) +{ + struct passwd *pw; + if ((pw = getpwuid(uid)) == NULL) + return "getpwuid failed"; + /* Get all the process's groups, with the pw_gid group first. */ + if (getgrouplist(pw->pw_name, pw->pw_gid, gid_list, size_ptr) < 0) + return "getgrouplist failed"; + /* Paranoia: is the default group not first in the list? */ + if (gid_list[0] != pw->pw_gid) { + int j; + for (j = 0; j < *size_ptr; j++) { + if (gid_list[j] == pw->pw_gid) { + gid_list[j] = gid_list[0]; + gid_list[0] = pw->pw_gid; + break; + } + } + } + return NULL; +} +#endif diff --git a/rsync/util.c b/rsync/util.c new file mode 100644 index 0000000..05aa86a --- /dev/null +++ b/rsync/util.c @@ -0,0 +1,1635 @@ +/* + * Utility routines used in rsync. + * + * Copyright (C) 1996-2000 Andrew Tridgell + * Copyright (C) 1996 Paul Mackerras + * Copyright (C) 2001, 2002 Martin Pool + * Copyright (C) 2003-2014 Wayne Davison + * + * 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, visit the http://fsf.org website. + */ + +#include "rsync.h" +#include "ifuncs.h" +#include "itypes.h" +#include "inums.h" + +extern int dry_run; +extern int module_id; +extern int protect_args; +extern int modify_window; +extern int relative_paths; +extern int preserve_times; +extern int preserve_xattrs; +extern int preallocate_files; +extern char *module_dir; +extern unsigned int module_dirlen; +extern char *partial_dir; +extern filter_rule_list daemon_filter_list; + +int sanitize_paths = 0; + +char curr_dir[MAXPATHLEN]; +unsigned int curr_dir_len; +int curr_dir_depth; /* This is only set for a sanitizing daemon. */ + +/* Set a fd into nonblocking mode. */ +void set_nonblocking(int fd) +{ + int val; + + if ((val = fcntl(fd, F_GETFL)) == -1) + return; + if (!(val & NONBLOCK_FLAG)) { + val |= NONBLOCK_FLAG; + fcntl(fd, F_SETFL, val); + } +} + +/* Set a fd into blocking mode. */ +void set_blocking(int fd) +{ + int val; + + if ((val = fcntl(fd, F_GETFL)) == -1) + return; + if (val & NONBLOCK_FLAG) { + val &= ~NONBLOCK_FLAG; + fcntl(fd, F_SETFL, val); + } +} + +/** + * Create a file descriptor pair - like pipe() but use socketpair if + * possible (because of blocking issues on pipes). + * + * Always set non-blocking. + */ +int fd_pair(int fd[2]) +{ + int ret; + +#ifdef HAVE_SOCKETPAIR + ret = socketpair(AF_UNIX, SOCK_STREAM, 0, fd); +#else + ret = pipe(fd); +#endif + + if (ret == 0) { + set_nonblocking(fd[0]); + set_nonblocking(fd[1]); + } + + return ret; +} + +void print_child_argv(const char *prefix, char **cmd) +{ + int cnt = 0; + rprintf(FCLIENT, "%s ", prefix); + for (; *cmd; cmd++) { + /* Look for characters that ought to be quoted. This + * is not a great quoting algorithm, but it's + * sufficient for a log message. */ + if (strspn(*cmd, "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "0123456789" + ",.-_=+@/") != strlen(*cmd)) { + rprintf(FCLIENT, "\"%s\" ", *cmd); + } else { + rprintf(FCLIENT, "%s ", *cmd); + } + cnt++; + } + rprintf(FCLIENT, " (%d args)\n", cnt); +} + +/* This returns 0 for success, 1 for a symlink if symlink time-setting + * is not possible, or -1 for any other error. */ +int set_modtime(const char *fname, time_t modtime, uint32 mod_nsec, mode_t mode) +{ + static int switch_step = 0; + + if (DEBUG_GTE(TIME, 1)) { + rprintf(FINFO, "set modtime of %s to (%ld) %s", + fname, (long)modtime, + asctime(localtime(&modtime))); + } + + switch (switch_step) { +#ifdef HAVE_UTIMENSAT +#include "case_N.h" + if (do_utimensat(fname, modtime, mod_nsec) == 0) + break; + if (errno != ENOSYS) + return -1; + switch_step++; + /* FALLTHROUGH */ +#endif + +#ifdef HAVE_LUTIMES +#include "case_N.h" + if (do_lutimes(fname, modtime, mod_nsec) == 0) + break; + if (errno != ENOSYS) + return -1; + switch_step++; + /* FALLTHROUGH */ +#endif + +#include "case_N.h" + switch_step++; + if (preserve_times & PRESERVE_LINK_TIMES) { + preserve_times &= ~PRESERVE_LINK_TIMES; + if (S_ISLNK(mode)) + return 1; + } + /* FALLTHROUGH */ + +#include "case_N.h" +#ifdef HAVE_UTIMES + if (do_utimes(fname, modtime, mod_nsec) == 0) + break; +#else + if (do_utime(fname, modtime, mod_nsec) == 0) + break; +#endif + + return -1; + } + + return 0; +} + +/* Create any necessary directories in fname. Any missing directories are + * created with default permissions. Returns < 0 on error, or the number + * of directories created. */ +int make_path(char *fname, int flags) +{ + char *end, *p; + int ret = 0; + + if (flags & MKP_SKIP_SLASH) { + while (*fname == '/') + fname++; + } + + while (*fname == '.' && fname[1] == '/') + fname += 2; + + if (flags & MKP_DROP_NAME) { + end = strrchr(fname, '/'); + if (!end) + return 0; + *end = '\0'; + } else + end = fname + strlen(fname); + + /* Try to find an existing dir, starting from the deepest dir. */ + for (p = end; ; ) { + if (dry_run) { + STRUCT_STAT st; + if (do_stat(fname, &st) == 0) { + if (S_ISDIR(st.st_mode)) + errno = EEXIST; + else + errno = ENOTDIR; + } + } else if (do_mkdir(fname, ACCESSPERMS) == 0) { + ret++; + break; + } + if (errno != ENOENT) { + if (errno != EEXIST) + ret = -ret - 1; + break; + } + while (1) { + if (p == fname) { + /* We got a relative path that doesn't exist, so assume that '.' + * is there and just break out and create the whole thing. */ + p = NULL; + goto double_break; + } + if (*--p == '/') { + if (p == fname) { + /* We reached the "/" dir, which we assume is there. */ + goto double_break; + } + *p = '\0'; + break; + } + } + } + double_break: + + /* Make all the dirs that we didn't find on the way here. */ + while (p != end) { + if (p) + *p = '/'; + else + p = fname; + p += strlen(p); + if (ret < 0) /* Skip mkdir on error, but keep restoring the path. */ + continue; + if (do_mkdir(fname, ACCESSPERMS) < 0) + ret = -ret - 1; + else + ret++; + } + + if (flags & MKP_DROP_NAME) + *end = '/'; + + return ret; +} + +/** + * Write @p len bytes at @p ptr to descriptor @p desc, retrying if + * interrupted. + * + * @retval len upon success + * + * @retval <0 write's (negative) error code + * + * Derived from GNU C's cccp.c. + */ +int full_write(int desc, const char *ptr, size_t len) +{ + int total_written; + + total_written = 0; + while (len > 0) { + int written = write(desc, ptr, len); + if (written < 0) { + if (errno == EINTR) + continue; + return written; + } + total_written += written; + ptr += written; + len -= written; + } + return total_written; +} + +/** + * Read @p len bytes at @p ptr from descriptor @p desc, retrying if + * interrupted. + * + * @retval >0 the actual number of bytes read + * + * @retval 0 for EOF + * + * @retval <0 for an error. + * + * Derived from GNU C's cccp.c. */ +static int safe_read(int desc, char *ptr, size_t len) +{ + int n_chars; + + if (len == 0) + return len; + + do { + n_chars = read(desc, ptr, len); + } while (n_chars < 0 && errno == EINTR); + + return n_chars; +} + +/* Copy a file. If ofd < 0, copy_file unlinks and opens the "dest" file. + * Otherwise, it just writes to and closes the provided file descriptor. + * In either case, if --xattrs are being preserved, the dest file will + * have its xattrs set from the source file. + * + * This is used in conjunction with the --temp-dir, --backup, and + * --copy-dest options. */ +int copy_file(const char *source, const char *dest, int ofd, mode_t mode) +{ + int ifd; + char buf[1024 * 8]; + int len; /* Number of bytes read into `buf'. */ +#ifdef PREALLOCATE_NEEDS_TRUNCATE + OFF_T preallocated_len = 0, offset = 0; +#endif + + if ((ifd = do_open(source, O_RDONLY, 0)) < 0) { + int save_errno = errno; + rsyserr(FERROR_XFER, errno, "open %s", full_fname(source)); + errno = save_errno; + return -1; + } + + if (ofd < 0) { + if (robust_unlink(dest) && errno != ENOENT) { + int save_errno = errno; + rsyserr(FERROR_XFER, errno, "unlink %s", full_fname(dest)); + errno = save_errno; + return -1; + } + +#ifdef SUPPORT_XATTRS + if (preserve_xattrs) + mode |= S_IWUSR; +#endif + mode &= INITACCESSPERMS; + if ((ofd = do_open(dest, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, mode)) < 0) { + int save_errno = errno; + rsyserr(FERROR_XFER, save_errno, "open %s", full_fname(dest)); + close(ifd); + errno = save_errno; + return -1; + } + } + +#ifdef SUPPORT_PREALLOCATION + if (preallocate_files) { + STRUCT_STAT srcst; + + /* Try to preallocate enough space for file's eventual length. Can + * reduce fragmentation on filesystems like ext4, xfs, and NTFS. */ + if (do_fstat(ifd, &srcst) < 0) + rsyserr(FWARNING, errno, "fstat %s", full_fname(source)); + else if (srcst.st_size > 0) { + if (do_fallocate(ofd, 0, srcst.st_size) == 0) { +#ifdef PREALLOCATE_NEEDS_TRUNCATE + preallocated_len = srcst.st_size; +#endif + } else + rsyserr(FWARNING, errno, "do_fallocate %s", full_fname(dest)); + } + } +#endif + + while ((len = safe_read(ifd, buf, sizeof buf)) > 0) { + if (full_write(ofd, buf, len) < 0) { + int save_errno = errno; + rsyserr(FERROR_XFER, errno, "write %s", full_fname(dest)); + close(ifd); + close(ofd); + errno = save_errno; + return -1; + } +#ifdef PREALLOCATE_NEEDS_TRUNCATE + offset += len; +#endif + } + + if (len < 0) { + int save_errno = errno; + rsyserr(FERROR_XFER, errno, "read %s", full_fname(source)); + close(ifd); + close(ofd); + errno = save_errno; + return -1; + } + + if (close(ifd) < 0) { + rsyserr(FWARNING, errno, "close failed on %s", + full_fname(source)); + } + +#ifdef PREALLOCATE_NEEDS_TRUNCATE + /* Source file might have shrunk since we fstatted it. + * Cut off any extra preallocated zeros from dest file. */ + if (offset < preallocated_len && do_ftruncate(ofd, offset) < 0) { + /* If we fail to truncate, the dest file may be wrong, so we + * must trigger the "partial transfer" error. */ + rsyserr(FERROR_XFER, errno, "ftruncate %s", full_fname(dest)); + } +#endif + + if (close(ofd) < 0) { + int save_errno = errno; + rsyserr(FERROR_XFER, errno, "close failed on %s", + full_fname(dest)); + errno = save_errno; + return -1; + } + +#ifdef SUPPORT_XATTRS + if (preserve_xattrs) + copy_xattrs(source, dest); +#endif + + return 0; +} + +/* MAX_RENAMES should be 10**MAX_RENAMES_DIGITS */ +#define MAX_RENAMES_DIGITS 3 +#define MAX_RENAMES 1000 + +/** + * Robust unlink: some OS'es (HPUX) refuse to unlink busy files, so + * rename to /.rsyncNNN instead. + * + * Note that successive rsync runs will shuffle the filenames around a + * bit as long as the file is still busy; this is because this function + * does not know if the unlink call is due to a new file coming in, or + * --delete trying to remove old .rsyncNNN files, hence it renames it + * each time. + **/ +int robust_unlink(const char *fname) +{ +#ifndef ETXTBSY + return do_unlink(fname); +#else + static int counter = 1; + int rc, pos, start; + char path[MAXPATHLEN]; + + rc = do_unlink(fname); + if (rc == 0 || errno != ETXTBSY) + return rc; + + if ((pos = strlcpy(path, fname, MAXPATHLEN)) >= MAXPATHLEN) + pos = MAXPATHLEN - 1; + + while (pos > 0 && path[pos-1] != '/') + pos--; + pos += strlcpy(path+pos, ".rsync", MAXPATHLEN-pos); + + if (pos > (MAXPATHLEN-MAX_RENAMES_DIGITS-1)) { + errno = ETXTBSY; + return -1; + } + + /* start where the last one left off to reduce chance of clashes */ + start = counter; + do { + snprintf(&path[pos], MAX_RENAMES_DIGITS+1, "%03d", counter); + if (++counter >= MAX_RENAMES) + counter = 1; + } while ((rc = access(path, 0)) == 0 && counter != start); + + if (INFO_GTE(MISC, 1)) { + rprintf(FWARNING, "renaming %s to %s because of text busy\n", + fname, path); + } + + /* maybe we should return rename()'s exit status? Nah. */ + if (do_rename(fname, path) != 0) { + errno = ETXTBSY; + return -1; + } + return 0; +#endif +} + +/* Returns 0 on successful rename, 1 if we successfully copied the file + * across filesystems, -2 if copy_file() failed, and -1 on other errors. + * If partialptr is not NULL and we need to do a copy, copy the file into + * the active partial-dir instead of over the destination file. */ +int robust_rename(const char *from, const char *to, const char *partialptr, + int mode) +{ + int tries = 4; + + while (tries--) { + if (do_rename(from, to) == 0) + return 0; + + switch (errno) { +#ifdef ETXTBSY + case ETXTBSY: + if (robust_unlink(to) != 0) { + errno = ETXTBSY; + return -1; + } + errno = ETXTBSY; + break; +#endif + case EXDEV: + if (partialptr) { + if (!handle_partial_dir(partialptr,PDIR_CREATE)) + return -2; + to = partialptr; + } + if (copy_file(from, to, -1, mode) != 0) + return -2; + do_unlink(from); + return 1; + default: + return -1; + } + } + return -1; +} + +static pid_t all_pids[10]; +static int num_pids; + +/** Fork and record the pid of the child. **/ +pid_t do_fork(void) +{ + pid_t newpid = fork(); + + if (newpid != 0 && newpid != -1) { + all_pids[num_pids++] = newpid; + } + return newpid; +} + +/** + * Kill all children. + * + * @todo It would be kind of nice to make sure that they are actually + * all our children before we kill them, because their pids may have + * been recycled by some other process. Perhaps when we wait for a + * child, we should remove it from this array. Alternatively we could + * perhaps use process groups, but I think that would not work on + * ancient Unix versions that don't support them. + **/ +void kill_all(int sig) +{ + int i; + + for (i = 0; i < num_pids; i++) { + /* Let's just be a little careful where we + * point that gun, hey? See kill(2) for the + * magic caused by negative values. */ + pid_t p = all_pids[i]; + + if (p == getpid()) + continue; + if (p <= 0) + continue; + + kill(p, sig); + } +} + +/** Lock a byte range in a open file */ +int lock_range(int fd, int offset, int len) +{ + struct flock lock; + + lock.l_type = F_WRLCK; + lock.l_whence = SEEK_SET; + lock.l_start = offset; + lock.l_len = len; + lock.l_pid = 0; + + return fcntl(fd,F_SETLK,&lock) == 0; +} + +#define ENSURE_MEMSPACE(buf, type, sz, req) \ + if ((req) > sz && !(buf = realloc_array(buf, type, sz = MAX(sz * 2, req)))) \ + out_of_memory("glob_expand") + +static inline void call_glob_match(const char *name, int len, int from_glob, + char *arg, int abpos, int fbpos); + +static struct glob_data { + char *arg_buf, *filt_buf, **argv; + int absize, fbsize, maxargs, argc; +} glob; + +static void glob_match(char *arg, int abpos, int fbpos) +{ + int len; + char *slash; + + while (*arg == '.' && arg[1] == '/') { + if (fbpos < 0) { + ENSURE_MEMSPACE(glob.filt_buf, char, glob.fbsize, glob.absize); + memcpy(glob.filt_buf, glob.arg_buf, abpos + 1); + fbpos = abpos; + } + ENSURE_MEMSPACE(glob.arg_buf, char, glob.absize, abpos + 3); + glob.arg_buf[abpos++] = *arg++; + glob.arg_buf[abpos++] = *arg++; + glob.arg_buf[abpos] = '\0'; + } + if ((slash = strchr(arg, '/')) != NULL) { + *slash = '\0'; + len = slash - arg; + } else + len = strlen(arg); + if (strpbrk(arg, "*?[")) { + struct dirent *di; + DIR *d; + + if (!(d = opendir(abpos ? glob.arg_buf : "."))) + return; + while ((di = readdir(d)) != NULL) { + char *dname = d_name(di); + if (dname[0] == '.' && (dname[1] == '\0' + || (dname[1] == '.' && dname[2] == '\0'))) + continue; + if (!wildmatch(arg, dname)) + continue; + call_glob_match(dname, strlen(dname), 1, + slash ? arg + len + 1 : NULL, + abpos, fbpos); + } + closedir(d); + } else { + call_glob_match(arg, len, 0, + slash ? arg + len + 1 : NULL, + abpos, fbpos); + } + if (slash) + *slash = '/'; +} + +static inline void call_glob_match(const char *name, int len, int from_glob, + char *arg, int abpos, int fbpos) +{ + char *use_buf; + + ENSURE_MEMSPACE(glob.arg_buf, char, glob.absize, abpos + len + 2); + memcpy(glob.arg_buf + abpos, name, len); + abpos += len; + glob.arg_buf[abpos] = '\0'; + + if (fbpos >= 0) { + ENSURE_MEMSPACE(glob.filt_buf, char, glob.fbsize, fbpos + len + 2); + memcpy(glob.filt_buf + fbpos, name, len); + fbpos += len; + glob.filt_buf[fbpos] = '\0'; + use_buf = glob.filt_buf; + } else + use_buf = glob.arg_buf; + + if (from_glob || (arg && len)) { + STRUCT_STAT st; + int is_dir; + + if (do_stat(glob.arg_buf, &st) != 0) + return; + is_dir = S_ISDIR(st.st_mode) != 0; + if (arg && !is_dir) + return; + + if (daemon_filter_list.head + && check_filter(&daemon_filter_list, FLOG, use_buf, is_dir) < 0) + return; + } + + if (arg) { + glob.arg_buf[abpos++] = '/'; + glob.arg_buf[abpos] = '\0'; + if (fbpos >= 0) { + glob.filt_buf[fbpos++] = '/'; + glob.filt_buf[fbpos] = '\0'; + } + glob_match(arg, abpos, fbpos); + } else { + ENSURE_MEMSPACE(glob.argv, char *, glob.maxargs, glob.argc + 1); + if (!(glob.argv[glob.argc++] = strdup(glob.arg_buf))) + out_of_memory("glob_match"); + } +} + +/* This routine performs wild-card expansion of the pathname in "arg". Any + * daemon-excluded files/dirs will not be matched by the wildcards. Returns 0 + * if a wild-card string is the only returned item (due to matching nothing). */ +int glob_expand(const char *arg, char ***argv_p, int *argc_p, int *maxargs_p) +{ + int ret, save_argc; + char *s; + + if (!arg) { + if (glob.filt_buf) + free(glob.filt_buf); + free(glob.arg_buf); + memset(&glob, 0, sizeof glob); + return -1; + } + + if (sanitize_paths) + s = sanitize_path(NULL, arg, "", 0, SP_KEEP_DOT_DIRS); + else { + s = strdup(arg); + if (!s) + out_of_memory("glob_expand"); + clean_fname(s, CFN_KEEP_DOT_DIRS + | CFN_KEEP_TRAILING_SLASH + | CFN_COLLAPSE_DOT_DOT_DIRS); + } + + ENSURE_MEMSPACE(glob.arg_buf, char, glob.absize, MAXPATHLEN); + *glob.arg_buf = '\0'; + + glob.argc = save_argc = *argc_p; + glob.argv = *argv_p; + glob.maxargs = *maxargs_p; + + ENSURE_MEMSPACE(glob.argv, char *, glob.maxargs, 100); + + glob_match(s, 0, -1); + + /* The arg didn't match anything, so add the failed arg to the list. */ + if (glob.argc == save_argc) { + ENSURE_MEMSPACE(glob.argv, char *, glob.maxargs, glob.argc + 1); + glob.argv[glob.argc++] = s; + ret = 0; + } else { + free(s); + ret = 1; + } + + *maxargs_p = glob.maxargs; + *argv_p = glob.argv; + *argc_p = glob.argc; + + return ret; +} + +/* This routine is only used in daemon mode. */ +void glob_expand_module(char *base1, char *arg, char ***argv_p, int *argc_p, int *maxargs_p) +{ + char *p, *s; + char *base = base1; + int base_len = strlen(base); + + if (!arg || !*arg) + return; + + if (strncmp(arg, base, base_len) == 0) + arg += base_len; + + if (protect_args) { + glob_expand(arg, argv_p, argc_p, maxargs_p); + return; + } + + if (!(arg = strdup(arg))) + out_of_memory("glob_expand_module"); + + if (asprintf(&base," %s/", base1) < 0) + out_of_memory("glob_expand_module"); + base_len++; + + for (s = arg; *s; s = p + base_len) { + if ((p = strstr(s, base)) != NULL) + *p = '\0'; /* split it at this point */ + glob_expand(s, argv_p, argc_p, maxargs_p); + if (!p) + break; + } + + free(arg); + free(base); +} + +/** + * Convert a string to lower case + **/ +void strlower(char *s) +{ + while (*s) { + if (isUpper(s)) + *s = toLower(s); + s++; + } +} + +/* Join strings p1 & p2 into "dest" with a guaranteed '/' between them. (If + * p1 ends with a '/', no extra '/' is inserted.) Returns the length of both + * strings + 1 (if '/' was inserted), regardless of whether the null-terminated + * string fits into destsize. */ +size_t pathjoin(char *dest, size_t destsize, const char *p1, const char *p2) +{ + size_t len = strlcpy(dest, p1, destsize); + if (len < destsize - 1) { + if (!len || dest[len-1] != '/') + dest[len++] = '/'; + if (len < destsize - 1) + len += strlcpy(dest + len, p2, destsize - len); + else { + dest[len] = '\0'; + len += strlen(p2); + } + } + else + len += strlen(p2) + 1; /* Assume we'd insert a '/'. */ + return len; +} + +/* Join any number of strings together, putting them in "dest". The return + * value is the length of all the strings, regardless of whether the null- + * terminated whole fits in destsize. Your list of string pointers must end + * with a NULL to indicate the end of the list. */ +size_t stringjoin(char *dest, size_t destsize, ...) +{ + va_list ap; + size_t len, ret = 0; + const char *src; + + va_start(ap, destsize); + while (1) { + if (!(src = va_arg(ap, const char *))) + break; + len = strlen(src); + ret += len; + if (destsize > 1) { + if (len >= destsize) + len = destsize - 1; + memcpy(dest, src, len); + destsize -= len; + dest += len; + } + } + *dest = '\0'; + va_end(ap); + + return ret; +} + +int count_dir_elements(const char *p) +{ + int cnt = 0, new_component = 1; + while (*p) { + if (*p++ == '/') + new_component = (*p != '.' || (p[1] != '/' && p[1] != '\0')); + else if (new_component) { + new_component = 0; + cnt++; + } + } + return cnt; +} + +/* Turns multiple adjacent slashes into a single slash (possible exception: + * the preserving of two leading slashes at the start), drops all leading or + * interior "." elements unless CFN_KEEP_DOT_DIRS is flagged. Will also drop + * a trailing '.' after a '/' if CFN_DROP_TRAILING_DOT_DIR is flagged, removes + * a trailing slash (perhaps after removing the aforementioned dot) unless + * CFN_KEEP_TRAILING_SLASH is flagged, and will also collapse ".." elements + * (except at the start) if CFN_COLLAPSE_DOT_DOT_DIRS is flagged. If the + * resulting name would be empty, returns ".". */ +int clean_fname(char *name, int flags) +{ + char *limit = name - 1, *t = name, *f = name; + int anchored; + + if (!name) + return 0; + +#define DOT_IS_DOT_DOT_DIR(bp) (bp[1] == '.' && (bp[2] == '/' || !bp[2])) + + if ((anchored = *f == '/') != 0) { + *t++ = *f++; +#ifdef __CYGWIN__ + /* If there are exactly 2 slashes at the start, preserve + * them. Would break daemon excludes unless the paths are + * really treated differently, so used this sparingly. */ + if (*f == '/' && f[1] != '/') + *t++ = *f++; +#endif + } else if (flags & CFN_KEEP_DOT_DIRS && *f == '.' && f[1] == '/') { + *t++ = *f++; + *t++ = *f++; + } else if (flags & CFN_REFUSE_DOT_DOT_DIRS && *f == '.' && DOT_IS_DOT_DOT_DIR(f)) + return -1; + while (*f) { + /* discard extra slashes */ + if (*f == '/') { + f++; + continue; + } + if (*f == '.') { + /* discard interior "." dirs */ + if (f[1] == '/' && !(flags & CFN_KEEP_DOT_DIRS)) { + f += 2; + continue; + } + if (f[1] == '\0' && flags & CFN_DROP_TRAILING_DOT_DIR) + break; + /* collapse ".." dirs */ + if (flags & (CFN_COLLAPSE_DOT_DOT_DIRS|CFN_REFUSE_DOT_DOT_DIRS) && DOT_IS_DOT_DOT_DIR(f)) { + char *s = t - 1; + if (flags & CFN_REFUSE_DOT_DOT_DIRS) + return -1; + if (s == name && anchored) { + f += 2; + continue; + } + while (s > limit && *--s != '/') {} + if (s != t - 1 && (s < name || *s == '/')) { + t = s + 1; + f += 2; + continue; + } + limit = t + 2; + } + } + while (*f && (*t++ = *f++) != '/') {} + } + + if (t > name+anchored && t[-1] == '/' && !(flags & CFN_KEEP_TRAILING_SLASH)) + t--; + if (t == name) + *t++ = '.'; + *t = '\0'; + +#undef DOT_IS_DOT_DOT_DIR + + return t - name; +} + +/* Make path appear as if a chroot had occurred. This handles a leading + * "/" (either removing it or expanding it) and any leading or embedded + * ".." components that attempt to escape past the module's top dir. + * + * If dest is NULL, a buffer is allocated to hold the result. It is legal + * to call with the dest and the path (p) pointing to the same buffer, but + * rootdir will be ignored to avoid expansion of the string. + * + * The rootdir string contains a value to use in place of a leading slash. + * Specify NULL to get the default of "module_dir". + * + * The depth var is a count of how many '..'s to allow at the start of the + * path. + * + * We also clean the path in a manner similar to clean_fname() but with a + * few differences: + * + * Turns multiple adjacent slashes into a single slash, gets rid of "." dir + * elements (INCLUDING a trailing dot dir), PRESERVES a trailing slash, and + * ALWAYS collapses ".." elements (except for those at the start of the + * string up to "depth" deep). If the resulting name would be empty, + * change it into a ".". */ +char *sanitize_path(char *dest, const char *p, const char *rootdir, int depth, + int flags) +{ + char *start, *sanp; + int rlen = 0, drop_dot_dirs = !relative_paths || !(flags & SP_KEEP_DOT_DIRS); + + if (dest != p) { + int plen = strlen(p); + if (*p == '/') { + if (!rootdir) + rootdir = module_dir; + rlen = strlen(rootdir); + depth = 0; + p++; + } + if (dest) { + if (rlen + plen + 1 >= MAXPATHLEN) + return NULL; + } else if (!(dest = new_array(char, rlen + plen + 1))) + out_of_memory("sanitize_path"); + if (rlen) { + memcpy(dest, rootdir, rlen); + if (rlen > 1) + dest[rlen++] = '/'; + } + } + + if (drop_dot_dirs) { + while (*p == '.' && p[1] == '/') + p += 2; + } + + start = sanp = dest + rlen; + /* This loop iterates once per filename component in p, pointing at + * the start of the name (past any prior slash) for each iteration. */ + while (*p) { + /* discard leading or extra slashes */ + if (*p == '/') { + p++; + continue; + } + if (drop_dot_dirs) { + if (*p == '.' && (p[1] == '/' || p[1] == '\0')) { + /* skip "." component */ + p++; + continue; + } + } + if (*p == '.' && p[1] == '.' && (p[2] == '/' || p[2] == '\0')) { + /* ".." component followed by slash or end */ + if (depth <= 0 || sanp != start) { + p += 2; + if (sanp != start) { + /* back up sanp one level */ + --sanp; /* now pointing at slash */ + while (sanp > start && sanp[-1] != '/') + sanp--; + } + continue; + } + /* allow depth levels of .. at the beginning */ + depth--; + /* move the virtual beginning to leave the .. alone */ + start = sanp + 3; + } + /* copy one component through next slash */ + while (*p && (*sanp++ = *p++) != '/') {} + } + if (sanp == dest) { + /* ended up with nothing, so put in "." component */ + *sanp++ = '.'; + } + *sanp = '\0'; + + return dest; +} + +/* Like chdir(), but it keeps track of the current directory (in the + * global "curr_dir"), and ensures that the path size doesn't overflow. + * Also cleans the path using the clean_fname() function. */ +int change_dir(const char *dir, int set_path_only) +{ + static int initialised, skipped_chdir; + unsigned int len; + + if (!initialised) { + initialised = 1; + if (getcwd(curr_dir, sizeof curr_dir - 1) == NULL) { + rsyserr(FERROR, errno, "getcwd()"); + exit_cleanup(RERR_FILESELECT); + } + curr_dir_len = strlen(curr_dir); + } + + if (!dir) /* this call was probably just to initialize */ + return 0; + + len = strlen(dir); + if (len == 1 && *dir == '.' && (!skipped_chdir || set_path_only)) + return 1; + + if (*dir == '/') { + if (len >= sizeof curr_dir) { + errno = ENAMETOOLONG; + return 0; + } + if (!set_path_only && chdir(dir)) + return 0; + skipped_chdir = set_path_only; + memcpy(curr_dir, dir, len + 1); + } else { + if (curr_dir_len + 1 + len >= sizeof curr_dir) { + errno = ENAMETOOLONG; + return 0; + } + if (!(curr_dir_len && curr_dir[curr_dir_len-1] == '/')) + curr_dir[curr_dir_len++] = '/'; + memcpy(curr_dir + curr_dir_len, dir, len + 1); + + if (!set_path_only && chdir(curr_dir)) { + curr_dir[curr_dir_len] = '\0'; + return 0; + } + skipped_chdir = set_path_only; + } + + curr_dir_len = clean_fname(curr_dir, CFN_COLLAPSE_DOT_DOT_DIRS | CFN_DROP_TRAILING_DOT_DIR); + if (sanitize_paths) { + if (module_dirlen > curr_dir_len) + module_dirlen = curr_dir_len; + curr_dir_depth = count_dir_elements(curr_dir + module_dirlen); + } + + if (DEBUG_GTE(CHDIR, 1) && !set_path_only) + rprintf(FINFO, "[%s] change_dir(%s)\n", who_am_i(), curr_dir); + + return 1; +} + +/* This will make a relative path absolute and clean it up via clean_fname(). + * Returns the string, which might be newly allocated, or NULL on error. */ +char *normalize_path(char *path, BOOL force_newbuf, unsigned int *len_ptr) +{ + unsigned int len; + + if (*path != '/') { /* Make path absolute. */ + int len = strlen(path); + if (curr_dir_len + 1 + len >= sizeof curr_dir) + return NULL; + curr_dir[curr_dir_len] = '/'; + memcpy(curr_dir + curr_dir_len + 1, path, len + 1); + if (!(path = strdup(curr_dir))) + out_of_memory("normalize_path"); + curr_dir[curr_dir_len] = '\0'; + } else if (force_newbuf) { + if (!(path = strdup(path))) + out_of_memory("normalize_path"); + } + + len = clean_fname(path, CFN_COLLAPSE_DOT_DOT_DIRS | CFN_DROP_TRAILING_DOT_DIR); + + if (len_ptr) + *len_ptr = len; + + return path; +} + +/** + * Return a quoted string with the full pathname of the indicated filename. + * The string " (in MODNAME)" may also be appended. The returned pointer + * remains valid until the next time full_fname() is called. + **/ +char *full_fname(const char *fn) +{ + static char *result = NULL; + char *m1, *m2, *m3; + char *p1, *p2; + + if (result) + free(result); + + if (*fn == '/') + p1 = p2 = ""; + else { + p1 = curr_dir + module_dirlen; + for (p2 = p1; *p2 == '/'; p2++) {} + if (*p2) + p2 = "/"; + } + if (module_id >= 0) { + m1 = " (in "; + m2 = lp_name(module_id); + m3 = ")"; + } else + m1 = m2 = m3 = ""; + + if (asprintf(&result, "\"%s%s%s\"%s%s%s", p1, p2, fn, m1, m2, m3) < 0) + out_of_memory("full_fname"); + + return result; +} + +static char partial_fname[MAXPATHLEN]; + +char *partial_dir_fname(const char *fname) +{ + char *t = partial_fname; + int sz = sizeof partial_fname; + const char *fn; + + if ((fn = strrchr(fname, '/')) != NULL) { + fn++; + if (*partial_dir != '/') { + int len = fn - fname; + strncpy(t, fname, len); /* safe */ + t += len; + sz -= len; + } + } else + fn = fname; + if ((int)pathjoin(t, sz, partial_dir, fn) >= sz) + return NULL; + if (daemon_filter_list.head) { + t = strrchr(partial_fname, '/'); + *t = '\0'; + if (check_filter(&daemon_filter_list, FLOG, partial_fname, 1) < 0) + return NULL; + *t = '/'; + if (check_filter(&daemon_filter_list, FLOG, partial_fname, 0) < 0) + return NULL; + } + + return partial_fname; +} + +/* If no --partial-dir option was specified, we don't need to do anything + * (the partial-dir is essentially '.'), so just return success. */ +int handle_partial_dir(const char *fname, int create) +{ + char *fn, *dir; + + if (fname != partial_fname) + return 1; + if (!create && *partial_dir == '/') + return 1; + if (!(fn = strrchr(partial_fname, '/'))) + return 1; + + *fn = '\0'; + dir = partial_fname; + if (create) { + STRUCT_STAT st; + int statret = do_lstat(dir, &st); + if (statret == 0 && !S_ISDIR(st.st_mode)) { + if (do_unlink(dir) < 0) { + *fn = '/'; + return 0; + } + statret = -1; + } + if (statret < 0 && do_mkdir(dir, 0700) < 0) { + *fn = '/'; + return 0; + } + } else + do_rmdir(dir); + *fn = '/'; + + return 1; +} + +/* Determine if a symlink points outside the current directory tree. + * This is considered "unsafe" because e.g. when mirroring somebody + * else's machine it might allow them to establish a symlink to + * /etc/passwd, and then read it through a web server. + * + * Returns 1 if unsafe, 0 if safe. + * + * Null symlinks and absolute symlinks are always unsafe. + * + * Basically here we are concerned with symlinks whose target contains + * "..", because this might cause us to walk back up out of the + * transferred directory. We are not allowed to go back up and + * reenter. + * + * "dest" is the target of the symlink in question. + * + * "src" is the top source directory currently applicable at the level + * of the referenced symlink. This is usually the symlink's full path + * (including its name), as referenced from the root of the transfer. */ +int unsafe_symlink(const char *dest, const char *src) +{ + const char *name, *slash; + int depth = 0; + + /* all absolute and null symlinks are unsafe */ + if (!dest || !*dest || *dest == '/') + return 1; + + /* find out what our safety margin is */ + for (name = src; (slash = strchr(name, '/')) != 0; name = slash+1) { + /* ".." segment starts the count over. "." segment is ignored. */ + if (*name == '.' && (name[1] == '/' || (name[1] == '.' && name[2] == '/'))) { + if (name[1] == '.') + depth = 0; + } else + depth++; + while (slash[1] == '/') slash++; /* just in case src isn't clean */ + } + if (*name == '.' && name[1] == '.' && name[2] == '\0') + depth = 0; + + for (name = dest; (slash = strchr(name, '/')) != 0; name = slash+1) { + if (*name == '.' && (name[1] == '/' || (name[1] == '.' && name[2] == '/'))) { + if (name[1] == '.') { + /* if at any point we go outside the current directory + then stop - it is unsafe */ + if (--depth < 0) + return 1; + } + } else + depth++; + while (slash[1] == '/') slash++; + } + if (*name == '.' && name[1] == '.' && name[2] == '\0') + depth--; + + return depth < 0; +} + +/* Return the date and time as a string. Some callers tweak returned buf. */ +char *timestring(time_t t) +{ + static char TimeBuf[200]; + struct tm *tm = localtime(&t); + char *p; + +#ifdef HAVE_STRFTIME + strftime(TimeBuf, sizeof TimeBuf - 1, "%Y/%m/%d %H:%M:%S", tm); +#else + strlcpy(TimeBuf, asctime(tm), sizeof TimeBuf); +#endif + + if ((p = strchr(TimeBuf, '\n')) != NULL) + *p = '\0'; + + return TimeBuf; +} + +/* Determine if two time_t values are equivalent (either exact, or in + * the modification timestamp window established by --modify-window). + * + * @retval 0 if the times should be treated as the same + * + * @retval +1 if the first is later + * + * @retval -1 if the 2nd is later + **/ +int cmp_time(time_t file1, time_t file2) +{ + if (file2 > file1) { + /* The final comparison makes sure that modify_window doesn't overflow a + * time_t, which would mean that file2 must be in the equality window. */ + if (!modify_window || (file2 > file1 + modify_window && file1 + modify_window > file1)) + return -1; + } else if (file1 > file2) { + if (!modify_window || (file1 > file2 + modify_window && file2 + modify_window > file2)) + return 1; + } + return 0; +} + +#ifdef __INSURE__XX +#include + +/** + This routine is a trick to immediately catch errors when debugging + with insure. A xterm with a gdb is popped up when insure catches + a error. It is Linux specific. +**/ +int _Insure_trap_error(int a1, int a2, int a3, int a4, int a5, int a6) +{ + static int (*fn)(); + int ret, pid_int = getpid(); + char *cmd; + + if (asprintf(&cmd, + "/usr/X11R6/bin/xterm -display :0 -T Panic -n Panic -e /bin/sh -c 'cat /tmp/ierrs.*.%d ; " + "gdb /proc/%d/exe %d'", pid_int, pid_int, pid_int) < 0) + return -1; + + if (!fn) { + static void *h; + h = dlopen("/usr/local/parasoft/insure++lite/lib.linux2/libinsure.so", RTLD_LAZY); + fn = dlsym(h, "_Insure_trap_error"); + } + + ret = fn(a1, a2, a3, a4, a5, a6); + + system(cmd); + + free(cmd); + + return ret; +} +#endif + +/* Take a filename and filename length and return the most significant + * filename suffix we can find. This ignores suffixes such as "~", + * ".bak", ".orig", ".~1~", etc. */ +const char *find_filename_suffix(const char *fn, int fn_len, int *len_ptr) +{ + const char *suf, *s; + BOOL had_tilde; + int s_len; + + /* One or more dots at the start aren't a suffix. */ + while (fn_len && *fn == '.') fn++, fn_len--; + + /* Ignore the ~ in a "foo~" filename. */ + if (fn_len > 1 && fn[fn_len-1] == '~') + fn_len--, had_tilde = True; + else + had_tilde = False; + + /* Assume we don't find an suffix. */ + suf = ""; + *len_ptr = 0; + + /* Find the last significant suffix. */ + for (s = fn + fn_len; fn_len > 1; ) { + while (*--s != '.' && s != fn) {} + if (s == fn) + break; + s_len = fn_len - (s - fn); + fn_len = s - fn; + if (s_len == 4) { + if (strcmp(s+1, "bak") == 0 + || strcmp(s+1, "old") == 0) + continue; + } else if (s_len == 5) { + if (strcmp(s+1, "orig") == 0) + continue; + } else if (s_len > 2 && had_tilde + && s[1] == '~' && isDigit(s + 2)) + continue; + *len_ptr = s_len; + suf = s; + if (s_len == 1) + break; + /* Determine if the suffix is all digits. */ + for (s++, s_len--; s_len > 0; s++, s_len--) { + if (!isDigit(s)) + return suf; + } + /* An all-digit suffix may not be that signficant. */ + s = suf; + } + + return suf; +} + +/* This is an implementation of the Levenshtein distance algorithm. It + * was implemented to avoid needing a two-dimensional matrix (to save + * memory). It was also tweaked to try to factor in the ASCII distance + * between changed characters as a minor distance quantity. The normal + * Levenshtein units of distance (each signifying a single change between + * the two strings) are defined as a "UNIT". */ + +#define UNIT (1 << 16) + +uint32 fuzzy_distance(const char *s1, unsigned len1, const char *s2, unsigned len2) +{ + uint32 a[MAXPATHLEN], diag, above, left, diag_inc, above_inc, left_inc; + int32 cost; + unsigned i1, i2; + + if (!len1 || !len2) { + if (!len1) { + s1 = s2; + len1 = len2; + } + for (i1 = 0, cost = 0; i1 < len1; i1++) + cost += s1[i1]; + return (int32)len1 * UNIT + cost; + } + + for (i2 = 0; i2 < len2; i2++) + a[i2] = (i2+1) * UNIT; + + for (i1 = 0; i1 < len1; i1++) { + diag = i1 * UNIT; + above = (i1+1) * UNIT; + for (i2 = 0; i2 < len2; i2++) { + left = a[i2]; + if ((cost = *((uchar*)s1+i1) - *((uchar*)s2+i2)) != 0) { + if (cost < 0) + cost = UNIT - cost; + else + cost = UNIT + cost; + } + diag_inc = diag + cost; + left_inc = left + UNIT + *((uchar*)s1+i1); + above_inc = above + UNIT + *((uchar*)s2+i2); + a[i2] = above = left < above + ? (left_inc < diag_inc ? left_inc : diag_inc) + : (above_inc < diag_inc ? above_inc : diag_inc); + diag = left; + } + } + + return a[len2-1]; +} + +#define BB_SLOT_SIZE (16*1024) /* Desired size in bytes */ +#define BB_PER_SLOT_BITS (BB_SLOT_SIZE * 8) /* Number of bits per slot */ +#define BB_PER_SLOT_INTS (BB_SLOT_SIZE / 4) /* Number of int32s per slot */ + +struct bitbag { + uint32 **bits; + int slot_cnt; +}; + +struct bitbag *bitbag_create(int max_ndx) +{ + struct bitbag *bb = new(struct bitbag); + bb->slot_cnt = (max_ndx + BB_PER_SLOT_BITS - 1) / BB_PER_SLOT_BITS; + + if (!(bb->bits = (uint32**)calloc(bb->slot_cnt, sizeof (uint32*)))) + out_of_memory("bitbag_create"); + + return bb; +} + +void bitbag_set_bit(struct bitbag *bb, int ndx) +{ + int slot = ndx / BB_PER_SLOT_BITS; + ndx %= BB_PER_SLOT_BITS; + + if (!bb->bits[slot]) { + if (!(bb->bits[slot] = (uint32*)calloc(BB_PER_SLOT_INTS, 4))) + out_of_memory("bitbag_set_bit"); + } + + bb->bits[slot][ndx/32] |= 1u << (ndx % 32); +} + +#if 0 /* not needed yet */ +void bitbag_clear_bit(struct bitbag *bb, int ndx) +{ + int slot = ndx / BB_PER_SLOT_BITS; + ndx %= BB_PER_SLOT_BITS; + + if (!bb->bits[slot]) + return; + + bb->bits[slot][ndx/32] &= ~(1u << (ndx % 32)); +} + +int bitbag_check_bit(struct bitbag *bb, int ndx) +{ + int slot = ndx / BB_PER_SLOT_BITS; + ndx %= BB_PER_SLOT_BITS; + + if (!bb->bits[slot]) + return 0; + + return bb->bits[slot][ndx/32] & (1u << (ndx % 32)) ? 1 : 0; +} +#endif + +/* Call this with -1 to start checking from 0. Returns -1 at the end. */ +int bitbag_next_bit(struct bitbag *bb, int after) +{ + uint32 bits, mask; + int i, ndx = after + 1; + int slot = ndx / BB_PER_SLOT_BITS; + ndx %= BB_PER_SLOT_BITS; + + mask = (1u << (ndx % 32)) - 1; + for (i = ndx / 32; slot < bb->slot_cnt; slot++, i = mask = 0) { + if (!bb->bits[slot]) + continue; + for ( ; i < BB_PER_SLOT_INTS; i++, mask = 0) { + if (!(bits = bb->bits[slot][i] & ~mask)) + continue; + /* The xor magic figures out the lowest enabled bit in + * bits, and the switch quickly computes log2(bit). */ + switch (bits ^ (bits & (bits-1))) { +#define LOG2(n) case 1u << n: return slot*BB_PER_SLOT_BITS + i*32 + n + LOG2(0); LOG2(1); LOG2(2); LOG2(3); + LOG2(4); LOG2(5); LOG2(6); LOG2(7); + LOG2(8); LOG2(9); LOG2(10); LOG2(11); + LOG2(12); LOG2(13); LOG2(14); LOG2(15); + LOG2(16); LOG2(17); LOG2(18); LOG2(19); + LOG2(20); LOG2(21); LOG2(22); LOG2(23); + LOG2(24); LOG2(25); LOG2(26); LOG2(27); + LOG2(28); LOG2(29); LOG2(30); LOG2(31); + } + return -1; /* impossible... */ + } + } + + return -1; +} + +void flist_ndx_push(flist_ndx_list *lp, int ndx) +{ + struct flist_ndx_item *item; + + if (!(item = new(struct flist_ndx_item))) + out_of_memory("flist_ndx_push"); + item->next = NULL; + item->ndx = ndx; + if (lp->tail) + lp->tail->next = item; + else + lp->head = item; + lp->tail = item; +} + +int flist_ndx_pop(flist_ndx_list *lp) +{ + struct flist_ndx_item *next; + int ndx; + + if (!lp->head) + return -1; + + ndx = lp->head->ndx; + next = lp->head->next; + free(lp->head); + lp->head = next; + if (!next) + lp->tail = NULL; + + return ndx; +} + +void *expand_item_list(item_list *lp, size_t item_size, + const char *desc, int incr) +{ + /* First time through, 0 <= 0, so list is expanded. */ + if (lp->malloced <= lp->count) { + void *new_ptr; + size_t new_size = lp->malloced; + if (incr < 0) + new_size += -incr; /* increase slowly */ + else if (new_size < (size_t)incr) + new_size += incr; + else + new_size *= 2; + if (new_size < lp->malloced) + overflow_exit("expand_item_list"); + /* Using _realloc_array() lets us pass the size, not a type. */ + new_ptr = _realloc_array(lp->items, item_size, new_size); + if (DEBUG_GTE(FLIST, 3)) { + rprintf(FINFO, "[%s] expand %s to %s bytes, did%s move\n", + who_am_i(), desc, big_num(new_size * item_size), + new_ptr == lp->items ? " not" : ""); + } + if (!new_ptr) + out_of_memory("expand_item_list"); + + lp->items = new_ptr; + lp->malloced = new_size; + } + return (char*)lp->items + (lp->count++ * item_size); +} diff --git a/rsync/util2.c b/rsync/util2.c new file mode 100644 index 0000000..6ffbcec --- /dev/null +++ b/rsync/util2.c @@ -0,0 +1,109 @@ +/* + * Utility routines used in rsync. + * + * Copyright (C) 1996-2000 Andrew Tridgell + * Copyright (C) 1996 Paul Mackerras + * Copyright (C) 2001, 2002 Martin Pool + * Copyright (C) 2003-2014 Wayne Davison + * + * 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, visit the http://fsf.org website. + */ + +#include "rsync.h" +#include "ifuncs.h" +#include "itypes.h" +#include "inums.h" + +extern int checksum_len; + +/** + * Sleep for a specified number of milliseconds. + * + * Always returns TRUE. (In the future it might return FALSE if + * interrupted.) + **/ +int msleep(int t) +{ + int tdiff = 0; + struct timeval tval, t1, t2; + + gettimeofday(&t1, NULL); + + while (tdiff < t) { + tval.tv_sec = (t-tdiff)/1000; + tval.tv_usec = 1000*((t-tdiff)%1000); + + errno = 0; + select(0,NULL,NULL, NULL, &tval); + + gettimeofday(&t2, NULL); + if (t2.tv_sec < t1.tv_sec) + t1 = t2; /* Time went backwards, so start over. */ + tdiff = (t2.tv_sec - t1.tv_sec)*1000 + + (t2.tv_usec - t1.tv_usec)/1000; + } + + return True; +} + +#define MALLOC_MAX 0x40000000 + +void *_new_array(unsigned long num, unsigned int size, int use_calloc) +{ + if (num >= MALLOC_MAX/size) + return NULL; + return use_calloc ? calloc(num, size) : malloc(num * size); +} + +void *_realloc_array(void *ptr, unsigned int size, size_t num) +{ + if (num >= MALLOC_MAX/size) + return NULL; + if (!ptr) + return malloc(size * num); + return realloc(ptr, size * num); +} + +const char *sum_as_hex(const char *sum) +{ + static char buf[MAX_DIGEST_LEN*2+1]; + int i, x1, x2; + char *c = buf + checksum_len*2; + + assert(c - buf < (int)sizeof buf); + + *c = '\0'; + + for (i = checksum_len; --i >= 0; ) { + x1 = CVAL(sum, i); + x2 = x1 >> 4; + x1 &= 0xF; + *--c = x1 <= 9 ? x1 + '0' : x1 + 'a' - 10; + *--c = x2 <= 9 ? x2 + '0' : x2 + 'a' - 10; + } + + return buf; +} + +NORETURN void out_of_memory(const char *str) +{ + rprintf(FERROR, "ERROR: out of memory in %s [%s]\n", str, who_am_i()); + exit_cleanup(RERR_MALLOC); +} + +NORETURN void overflow_exit(const char *str) +{ + rprintf(FERROR, "ERROR: buffer overflow in %s [%s]\n", str, who_am_i()); + exit_cleanup(RERR_MALLOC); +} diff --git a/rsync/wildtest.c b/rsync/wildtest.c new file mode 100644 index 0000000..19809b6 --- /dev/null +++ b/rsync/wildtest.c @@ -0,0 +1,221 @@ +/* + * Test suite for the wildmatch code. + * + * Copyright (C) 2003-2014 Wayne Davison + * + * 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, visit the http://fsf.org website. + */ + +/*#define COMPARE_WITH_FNMATCH*/ + +#define WILD_TEST_ITERATIONS +#include "lib/wildmatch.c" + +#include + +#ifdef COMPARE_WITH_FNMATCH +#include + +int fnmatch_errors = 0; +#endif + +int wildmatch_errors = 0; + +typedef char bool; + +int output_iterations = 0; +int explode_mod = 0; +int empties_mod = 0; +int empty_at_start = 0; +int empty_at_end = 0; + +static struct poptOption long_options[] = { + /* longName, shortName, argInfo, argPtr, value, descrip, argDesc */ + {"iterations", 'i', POPT_ARG_NONE, &output_iterations, 0, 0, 0}, + {"empties", 'e', POPT_ARG_STRING, 0, 'e', 0, 0}, + {"explode", 'x', POPT_ARG_INT, &explode_mod, 0, 0, 0}, + {0,0,0,0, 0, 0, 0} +}; + +/* match just at the start of string (anchored tests) */ +static void +run_test(int line, bool matches, +#ifdef COMPARE_WITH_FNMATCH + bool same_as_fnmatch, +#endif + const char *text, const char *pattern) +{ + bool matched; +#ifdef COMPARE_WITH_FNMATCH + bool fn_matched; + int flags = strstr(pattern, "**")? 0 : FNM_PATHNAME; +#endif + + if (explode_mod) { + char buf[MAXPATHLEN*2], *texts[MAXPATHLEN]; + int pos = 0, cnt = 0, ndx = 0, len = strlen(text); + + if (empty_at_start) + texts[ndx++] = ""; + /* An empty string must turn into at least one empty array item. */ + while (1) { + texts[ndx] = buf + ndx * (explode_mod + 1); + strlcpy(texts[ndx++], text + pos, explode_mod + 1); + if (pos + explode_mod >= len) + break; + pos += explode_mod; + if (!(++cnt % empties_mod)) + texts[ndx++] = ""; + } + if (empty_at_end) + texts[ndx++] = ""; + texts[ndx] = NULL; + matched = wildmatch_array(pattern, (const char**)texts, 0); + } else + matched = wildmatch(pattern, text); +#ifdef COMPARE_WITH_FNMATCH + fn_matched = !fnmatch(pattern, text, flags); +#endif + if (matched != matches) { + printf("wildmatch failure on line %d:\n %s\n %s\n expected %s match\n", + line, text, pattern, matches? "a" : "NO"); + wildmatch_errors++; + } +#ifdef COMPARE_WITH_FNMATCH + if (fn_matched != (matches ^ !same_as_fnmatch)) { + printf("fnmatch disagreement on line %d:\n %s\n %s\n expected %s match\n", + line, text, pattern, matches ^ !same_as_fnmatch? "a" : "NO"); + fnmatch_errors++; + } +#endif + if (output_iterations) { + printf("%d: \"%s\" iterations = %d\n", line, pattern, + wildmatch_iteration_count); + } +} + +int +main(int argc, char **argv) +{ + char buf[2048], *s, *string[2], *end[2]; + const char *arg; + FILE *fp; + int opt, line, i, flag[2]; + poptContext pc = poptGetContext("wildtest", argc, (const char**)argv, + long_options, 0); + + while ((opt = poptGetNextOpt(pc)) != -1) { + switch (opt) { + case 'e': + arg = poptGetOptArg(pc); + empties_mod = atoi(arg); + if (strchr(arg, 's')) + empty_at_start = 1; + if (strchr(arg, 'e')) + empty_at_end = 1; + if (!explode_mod) + explode_mod = 1024; + break; + default: + fprintf(stderr, "%s: %s\n", + poptBadOption(pc, POPT_BADOPTION_NOALIAS), + poptStrerror(opt)); + exit(1); + } + } + + if (explode_mod && !empties_mod) + empties_mod = 1024; + + argv = (char**)poptGetArgs(pc); + if (!argv || argv[1]) { + fprintf(stderr, "Usage: wildtest [OPTIONS] TESTFILE\n"); + exit(1); + } + + if ((fp = fopen(*argv, "r")) == NULL) { + fprintf(stderr, "Unable to open %s\n", *argv); + exit(1); + } + + line = 0; + while (fgets(buf, sizeof buf, fp)) { + line++; + if (*buf == '#' || *buf == '\n') + continue; + for (s = buf, i = 0; i <= 1; i++) { + if (*s == '1') + flag[i] = 1; + else if (*s == '0') + flag[i] = 0; + else + flag[i] = -1; + if (*++s != ' ' && *s != '\t') + flag[i] = -1; + if (flag[i] < 0) { + fprintf(stderr, "Invalid flag syntax on line %d of %s:\n%s", + line, *argv, buf); + exit(1); + } + while (*++s == ' ' || *s == '\t') {} + } + for (i = 0; i <= 1; i++) { + if (*s == '\'' || *s == '"' || *s == '`') { + char quote = *s++; + string[i] = s; + while (*s && *s != quote) s++; + if (!*s) { + fprintf(stderr, "Unmatched quote on line %d of %s:\n%s", + line, *argv, buf); + exit(1); + } + end[i] = s; + } + else { + if (!*s || *s == '\n') { + fprintf(stderr, "Not enough strings on line %d of %s:\n%s", + line, *argv, buf); + exit(1); + } + string[i] = s; + while (*++s && *s != ' ' && *s != '\t' && *s != '\n') {} + end[i] = s; + } + while (*++s == ' ' || *s == '\t') {} + } + *end[0] = *end[1] = '\0'; + run_test(line, flag[0], +#ifdef COMPARE_WITH_FNMATCH + flag[1], +#endif + string[0], string[1]); + } + + if (!wildmatch_errors) + fputs("No", stdout); + else + printf("%d", wildmatch_errors); + printf(" wildmatch error%s found.\n", wildmatch_errors == 1? "" : "s"); + +#ifdef COMPARE_WITH_FNMATCH + if (!fnmatch_errors) + fputs("No", stdout); + else + printf("%d", fnmatch_errors); + printf(" fnmatch error%s found.\n", fnmatch_errors == 1? "" : "s"); + +#endif + + return 0; +} diff --git a/rsync/wildtest.txt b/rsync/wildtest.txt new file mode 100644 index 0000000..42c1678 --- /dev/null +++ b/rsync/wildtest.txt @@ -0,0 +1,165 @@ +# Input is in the following format (all items white-space separated): +# +# The first two items are 1 or 0 indicating if the wildmat call is expected to +# succeed and if fnmatch works the same way as wildmat, respectively. After +# that is a text string for the match, and a pattern string. Strings can be +# quoted (if desired) in either double or single quotes, as well as backticks. +# +# MATCH FNMATCH_SAME "text to match" 'pattern to use' + +# Basic wildmat features +1 1 foo foo +0 1 foo bar +1 1 '' "" +1 1 foo ??? +0 1 foo ?? +1 1 foo * +1 1 foo f* +0 1 foo *f +1 1 foo *foo* +1 1 foobar *ob*a*r* +1 1 aaaaaaabababab *ab +1 1 foo* foo\* +0 1 foobar foo\*bar +1 1 f\oo f\\oo +1 1 ball *[al]? +0 1 ten [ten] +1 1 ten **[!te] +0 1 ten **[!ten] +1 1 ten t[a-g]n +0 1 ten t[!a-g]n +1 1 ton t[!a-g]n +1 1 ton t[^a-g]n +1 1 a]b a[]]b +1 1 a-b a[]-]b +1 1 a]b a[]-]b +0 1 aab a[]-]b +1 1 aab a[]a-]b +1 1 ] ] + +# Extended slash-matching features +0 1 foo/baz/bar foo*bar +1 1 foo/baz/bar foo**bar +0 1 foo/bar foo?bar +0 1 foo/bar foo[/]bar +0 1 foo/bar f[^eiu][^eiu][^eiu][^eiu][^eiu]r +1 1 foo-bar f[^eiu][^eiu][^eiu][^eiu][^eiu]r +0 1 foo **/foo +1 1 /foo **/foo +1 1 bar/baz/foo **/foo +0 1 bar/baz/foo */foo +0 0 foo/bar/baz **/bar* +1 1 deep/foo/bar/baz **/bar/* +0 1 deep/foo/bar/baz/ **/bar/* +1 1 deep/foo/bar/baz/ **/bar/** +0 1 deep/foo/bar **/bar/* +1 1 deep/foo/bar/ **/bar/** +1 1 foo/bar/baz **/bar** +1 1 foo/bar/baz/x */bar/** +0 0 deep/foo/bar/baz/x */bar/** +1 1 deep/foo/bar/baz/x **/bar/*/* + +# Various additional tests +0 1 acrt a[c-c]st +1 1 acrt a[c-c]rt +0 1 ] [!]-] +1 1 a [!]-] +0 1 '' \ +0 1 \ \ +0 1 /\ */\ +1 1 /\ */\\ +1 1 foo foo +1 1 @foo @foo +0 1 foo @foo +1 1 [ab] \[ab] +1 1 [ab] [[]ab] +1 1 [ab] [[:]ab] +0 1 [ab] [[::]ab] +1 1 [ab] [[:digit]ab] +1 1 [ab] [\[:]ab] +1 1 ?a?b \??\?b +1 1 abc \a\b\c +0 1 foo '' +1 1 foo/bar/baz/to **/t[o] + +# Character class tests +1 1 a1B [[:alpha:]][[:digit:]][[:upper:]] +0 1 a [[:digit:][:upper:][:space:]] +1 1 A [[:digit:][:upper:][:space:]] +1 1 1 [[:digit:][:upper:][:space:]] +0 1 1 [[:digit:][:upper:][:spaci:]] +1 1 ' ' [[:digit:][:upper:][:space:]] +0 1 . [[:digit:][:upper:][:space:]] +1 1 . [[:digit:][:punct:][:space:]] +1 1 5 [[:xdigit:]] +1 1 f [[:xdigit:]] +1 1 D [[:xdigit:]] +1 1 _ [[:alnum:][:alpha:][:blank:][:cntrl:][:digit:][:graph:][:lower:][:print:][:punct:][:space:][:upper:][:xdigit:]] +#1 1 … [^[:alnum:][:alpha:][:blank:][:cntrl:][:digit:][:graph:][:lower:][:print:][:punct:][:space:][:upper:][:xdigit:]] +1 1  [^[:alnum:][:alpha:][:blank:][:digit:][:graph:][:lower:][:print:][:punct:][:space:][:upper:][:xdigit:]] +1 1 . [^[:alnum:][:alpha:][:blank:][:cntrl:][:digit:][:lower:][:space:][:upper:][:xdigit:]] +1 1 5 [a-c[:digit:]x-z] +1 1 b [a-c[:digit:]x-z] +1 1 y [a-c[:digit:]x-z] +0 1 q [a-c[:digit:]x-z] + +# Additional tests, including some malformed wildmats +1 1 ] [\\-^] +0 1 [ [\\-^] +1 1 - [\-_] +1 1 ] [\]] +0 1 \] [\]] +0 1 \ [\]] +0 1 ab a[]b +0 1 a[]b a[]b +0 1 ab[ ab[ +0 1 ab [! +0 1 ab [- +1 1 - [-] +0 1 - [a- +0 1 - [!a- +1 1 - [--A] +1 1 5 [--A] +1 1 ' ' '[ --]' +1 1 $ '[ --]' +1 1 - '[ --]' +0 1 0 '[ --]' +1 1 - [---] +1 1 - [------] +0 1 j [a-e-n] +1 1 - [a-e-n] +1 1 a [!------] +0 1 [ []-a] +1 1 ^ []-a] +0 1 ^ [!]-a] +1 1 [ [!]-a] +1 1 ^ [a^bc] +1 1 -b] [a-]b] +0 1 \ [\] +1 1 \ [\\] +0 1 \ [!\\] +1 1 G [A-\\] +0 1 aaabbb b*a +0 1 aabcaa *ba* +1 1 , [,] +1 1 , [\\,] +1 1 \ [\\,] +1 1 - [,-.] +0 1 + [,-.] +0 1 -.] [,-.] +1 1 2 [\1-\3] +1 1 3 [\1-\3] +0 1 4 [\1-\3] +1 1 \ [[-\]] +1 1 [ [[-\]] +1 1 ] [[-\]] +0 1 - [[-\]] + +# Test recursion and the abort code (use "wildtest -i" to see iteration counts) +1 1 -adobe-courier-bold-o-normal--12-120-75-75-m-70-iso8859-1 -*-*-*-*-*-*-12-*-*-*-m-*-*-* +0 1 -adobe-courier-bold-o-normal--12-120-75-75-X-70-iso8859-1 -*-*-*-*-*-*-12-*-*-*-m-*-*-* +0 1 -adobe-courier-bold-o-normal--12-120-75-75-/-70-iso8859-1 -*-*-*-*-*-*-12-*-*-*-m-*-*-* +1 1 /adobe/courier/bold/o/normal//12/120/75/75/m/70/iso8859/1 /*/*/*/*/*/*/12/*/*/*/m/*/*/* +0 1 /adobe/courier/bold/o/normal//12/120/75/75/X/70/iso8859/1 /*/*/*/*/*/*/12/*/*/*/m/*/*/* +1 1 abcd/abcdefg/abcdefghijk/abcdefghijklmnop.txt **/*a*b*g*n*t +0 1 abcd/abcdefg/abcdefghijk/abcdefghijklmnop.txtz **/*a*b*g*n*t diff --git a/rsync/xattrs.c b/rsync/xattrs.c new file mode 100644 index 0000000..57d40e1 --- /dev/null +++ b/rsync/xattrs.c @@ -0,0 +1,1108 @@ +/* + * Extended Attribute support for rsync. + * Written by Jay Fenlason, vaguely based on the ACLs patch. + * + * Copyright (C) 2004 Red Hat, Inc. + * Copyright (C) 2006-2014 Wayne Davison + * + * 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, visit the http://fsf.org website. + */ + +#include "rsync.h" +#include "ifuncs.h" +#include "inums.h" +#include "lib/sysxattrs.h" + +#ifdef SUPPORT_XATTRS + +extern int dry_run; +extern int am_root; +extern int am_sender; +extern int am_generator; +extern int read_only; +extern int list_only; +extern int preserve_xattrs; +extern int preserve_links; +extern int preserve_devices; +extern int preserve_specials; +extern int checksum_seed; + +#define RSYNC_XAL_INITIAL 5 +#define RSYNC_XAL_LIST_INITIAL 100 + +#define MAX_FULL_DATUM 32 + +#define HAS_PREFIX(str, prfx) (*(str) == *(prfx) \ + && strncmp(str, prfx, sizeof (prfx) - 1) == 0) + +#define XATTR_ABBREV(x) ((size_t)((x).name - (x).datum) < (x).datum_len) + +#define XSTATE_ABBREV 1 +#define XSTATE_DONE 2 +#define XSTATE_TODO 3 + +#define USER_PREFIX "user." +#define UPRE_LEN ((int)sizeof USER_PREFIX - 1) +#define SYSTEM_PREFIX "system." +#define SPRE_LEN ((int)sizeof SYSTEM_PREFIX - 1) + +#ifdef HAVE_LINUX_XATTRS +#define MIGHT_NEED_RPRE (am_root < 0) +#define RSYNC_PREFIX USER_PREFIX "rsync." +#else +#define MIGHT_NEED_RPRE am_root +#define RSYNC_PREFIX "rsync." +#endif +#define RPRE_LEN ((int)sizeof RSYNC_PREFIX - 1) + +#define XSTAT_SUFFIX "stat" +#define XSTAT_ATTR RSYNC_PREFIX "%" XSTAT_SUFFIX +#define XACC_ACL_SUFFIX "aacl" +#define XACC_ACL_ATTR RSYNC_PREFIX "%" XACC_ACL_SUFFIX +#define XDEF_ACL_SUFFIX "dacl" +#define XDEF_ACL_ATTR RSYNC_PREFIX "%" XDEF_ACL_SUFFIX + +typedef struct { + char *datum, *name; + size_t datum_len, name_len; + int num; +} rsync_xa; + +static size_t namebuf_len = 0; +static char *namebuf = NULL; + +static item_list empty_xattr = EMPTY_ITEM_LIST; +static item_list rsync_xal_l = EMPTY_ITEM_LIST; + +static size_t prior_xattr_count = (size_t)-1; + +/* ------------------------------------------------------------------------- */ + +static void rsync_xal_free(item_list *xalp) +{ + size_t i; + rsync_xa *rxas = xalp->items; + + if (!xalp->malloced) + return; + + for (i = 0; i < xalp->count; i++) { + free(rxas[i].datum); + /*free(rxas[i].name);*/ + } + free(xalp->items); +} + +void free_xattr(stat_x *sxp) +{ + if (!sxp->xattr) + return; + rsync_xal_free(sxp->xattr); + free(sxp->xattr); + sxp->xattr = NULL; +} + +static int rsync_xal_compare_names(const void *x1, const void *x2) +{ + const rsync_xa *xa1 = x1; + const rsync_xa *xa2 = x2; + return strcmp(xa1->name, xa2->name); +} + +static ssize_t get_xattr_names(const char *fname) +{ + ssize_t list_len; + int64 arg; + + if (!namebuf) { + namebuf_len = 1024; + namebuf = new_array(char, namebuf_len); + if (!namebuf) + out_of_memory("get_xattr_names"); + } + + while (1) { + /* The length returned includes all the '\0' terminators. */ + list_len = sys_llistxattr(fname, namebuf, namebuf_len); + if (list_len >= 0) { + if ((size_t)list_len <= namebuf_len) + break; + } else if (errno == ENOTSUP) + return 0; + else if (errno != ERANGE) { + arg = namebuf_len; + got_error: + rsyserr(FERROR_XFER, errno, + "get_xattr_names: llistxattr(\"%s\",%s) failed", + full_fname(fname), big_num(arg)); + return -1; + } + list_len = sys_llistxattr(fname, NULL, 0); + if (list_len < 0) { + arg = 0; + goto got_error; + } + if (namebuf_len) + free(namebuf); + namebuf_len = list_len + 1024; + namebuf = new_array(char, namebuf_len); + if (!namebuf) + out_of_memory("get_xattr_names"); + } + + return list_len; +} + +/* On entry, the *len_ptr parameter contains the size of the extra space we + * should allocate when we create a buffer for the data. On exit, it contains + * the length of the datum. */ +static char *get_xattr_data(const char *fname, const char *name, size_t *len_ptr, + int no_missing_error) +{ + size_t datum_len = sys_lgetxattr(fname, name, NULL, 0); + size_t extra_len = *len_ptr; + char *ptr; + + *len_ptr = datum_len; + + if (datum_len == (size_t)-1) { + if (errno == ENOTSUP || no_missing_error) + return NULL; + rsyserr(FERROR_XFER, errno, + "get_xattr_data: lgetxattr(\"%s\",\"%s\",0) failed", + full_fname(fname), name); + return NULL; + } + + if (!datum_len && !extra_len) + extra_len = 1; /* request non-zero amount of memory */ + if (datum_len + extra_len < datum_len) + overflow_exit("get_xattr_data"); + if (!(ptr = new_array(char, datum_len + extra_len))) + out_of_memory("get_xattr_data"); + + if (datum_len) { + size_t len = sys_lgetxattr(fname, name, ptr, datum_len); + if (len != datum_len) { + if (len == (size_t)-1) { + rsyserr(FERROR_XFER, errno, + "get_xattr_data: lgetxattr(\"%s\",\"%s\",%ld)" + " failed", full_fname(fname), name, (long)datum_len); + } else { + rprintf(FERROR_XFER, + "get_xattr_data: lgetxattr(\"%s\",\"%s\",%ld)" + " returned %ld\n", full_fname(fname), name, + (long)datum_len, (long)len); + } + free(ptr); + return NULL; + } + } + + return ptr; +} + +static int rsync_xal_get(const char *fname, item_list *xalp) +{ + ssize_t list_len, name_len; + size_t datum_len, name_offset; + char *name, *ptr; +#ifdef HAVE_LINUX_XATTRS + int user_only = am_sender ? 0 : !am_root; +#endif + rsync_xa *rxa; + int count; + + /* This puts the name list into the "namebuf" buffer. */ + if ((list_len = get_xattr_names(fname)) < 0) + return -1; + + for (name = namebuf; list_len > 0; name += name_len) { + name_len = strlen(name) + 1; + list_len -= name_len; + +#ifdef HAVE_LINUX_XATTRS + /* We always ignore the system namespace, and non-root + * ignores everything but the user namespace. */ + if (user_only ? !HAS_PREFIX(name, USER_PREFIX) + : HAS_PREFIX(name, SYSTEM_PREFIX)) + continue; +#endif + + /* No rsync.%FOO attributes are copied w/o 2 -X options. */ + if (name_len > RPRE_LEN && name[RPRE_LEN] == '%' + && HAS_PREFIX(name, RSYNC_PREFIX)) { + if ((am_sender && preserve_xattrs < 2) + || (am_root < 0 + && (strcmp(name+RPRE_LEN+1, XSTAT_SUFFIX) == 0 + || strcmp(name+RPRE_LEN+1, XACC_ACL_SUFFIX) == 0 + || strcmp(name+RPRE_LEN+1, XDEF_ACL_SUFFIX) == 0))) + continue; + } + + datum_len = name_len; /* Pass extra size to get_xattr_data() */ + if (!(ptr = get_xattr_data(fname, name, &datum_len, 0))) + return -1; + + if (datum_len > MAX_FULL_DATUM) { + /* For large datums, we store a flag and a checksum. */ + name_offset = 1 + MAX_DIGEST_LEN; + sum_init(checksum_seed); + sum_update(ptr, datum_len); + free(ptr); + + if (!(ptr = new_array(char, name_offset + name_len))) + out_of_memory("rsync_xal_get"); + *ptr = XSTATE_ABBREV; + sum_end(ptr + 1); + } else + name_offset = datum_len; + + rxa = EXPAND_ITEM_LIST(xalp, rsync_xa, RSYNC_XAL_INITIAL); + rxa->name = ptr + name_offset; + memcpy(rxa->name, name, name_len); + rxa->datum = ptr; + rxa->name_len = name_len; + rxa->datum_len = datum_len; + } + count = xalp->count; + rxa = xalp->items; + if (count > 1) + qsort(rxa, count, sizeof (rsync_xa), rsync_xal_compare_names); + for (rxa += count-1; count; count--, rxa--) + rxa->num = count; + return 0; +} + +/* Read the xattr(s) for this filename. */ +int get_xattr(const char *fname, stat_x *sxp) +{ + sxp->xattr = new(item_list); + *sxp->xattr = empty_xattr; + + if (S_ISREG(sxp->st.st_mode) || S_ISDIR(sxp->st.st_mode)) { + /* Everyone supports this. */ + } else if (S_ISLNK(sxp->st.st_mode)) { +#ifndef NO_SYMLINK_XATTRS + if (!preserve_links) +#endif + return 0; + } else if (IS_SPECIAL(sxp->st.st_mode)) { +#ifndef NO_SPECIAL_XATTRS + if (!preserve_specials) +#endif + return 0; + } else if (IS_DEVICE(sxp->st.st_mode)) { +#ifndef NO_DEVICE_XATTRS + if (!preserve_devices) +#endif + return 0; + } else if (IS_MISSING_FILE(sxp->st)) + return 0; + + if (rsync_xal_get(fname, sxp->xattr) < 0) { + free_xattr(sxp); + return -1; + } + return 0; +} + +int copy_xattrs(const char *source, const char *dest) +{ + ssize_t list_len, name_len; + size_t datum_len; + char *name, *ptr; +#ifdef HAVE_LINUX_XATTRS + int user_only = am_sender ? 0 : am_root <= 0; +#endif + + /* This puts the name list into the "namebuf" buffer. */ + if ((list_len = get_xattr_names(source)) < 0) + return -1; + + for (name = namebuf; list_len > 0; name += name_len) { + name_len = strlen(name) + 1; + list_len -= name_len; + +#ifdef HAVE_LINUX_XATTRS + /* We always ignore the system namespace, and non-root + * ignores everything but the user namespace. */ + if (user_only ? !HAS_PREFIX(name, USER_PREFIX) + : HAS_PREFIX(name, SYSTEM_PREFIX)) + continue; +#endif + + datum_len = 0; + if (!(ptr = get_xattr_data(source, name, &datum_len, 0))) + return -1; + if (sys_lsetxattr(dest, name, ptr, datum_len) < 0) { + int save_errno = errno ? errno : EINVAL; + rsyserr(FERROR_XFER, errno, + "copy_xattrs: lsetxattr(\"%s\",\"%s\") failed", + full_fname(dest), name); + errno = save_errno; + return -1; + } + free(ptr); + } + + return 0; +} + +static int find_matching_xattr(item_list *xalp) +{ + size_t i, j; + item_list *lst = rsync_xal_l.items; + + for (i = 0; i < rsync_xal_l.count; i++) { + rsync_xa *rxas1 = lst[i].items; + rsync_xa *rxas2 = xalp->items; + + /* Wrong number of elements? */ + if (lst[i].count != xalp->count) + continue; + /* any elements different? */ + for (j = 0; j < xalp->count; j++) { + if (rxas1[j].name_len != rxas2[j].name_len + || rxas1[j].datum_len != rxas2[j].datum_len + || strcmp(rxas1[j].name, rxas2[j].name)) + break; + if (rxas1[j].datum_len > MAX_FULL_DATUM) { + if (memcmp(rxas1[j].datum + 1, + rxas2[j].datum + 1, + MAX_DIGEST_LEN) != 0) + break; + } else { + if (memcmp(rxas1[j].datum, rxas2[j].datum, + rxas2[j].datum_len)) + break; + } + } + /* no differences found. This is The One! */ + if (j == xalp->count) + return i; + } + + return -1; +} + +/* Store *xalp on the end of rsync_xal_l */ +static void rsync_xal_store(item_list *xalp) +{ + item_list *new_lst = EXPAND_ITEM_LIST(&rsync_xal_l, item_list, RSYNC_XAL_LIST_INITIAL); + /* Since the following call starts a new list, we know it will hold the + * entire initial-count, not just enough space for one new item. */ + *new_lst = empty_xattr; + (void)EXPAND_ITEM_LIST(new_lst, rsync_xa, xalp->count); + memcpy(new_lst->items, xalp->items, xalp->count * sizeof (rsync_xa)); + new_lst->count = xalp->count; + xalp->count = 0; +} + +/* Send the make_xattr()-generated xattr list for this flist entry. */ +int send_xattr(int f, stat_x *sxp) +{ + int ndx = find_matching_xattr(sxp->xattr); + + /* Send 0 (-1 + 1) to indicate that literal xattr data follows. */ + write_varint(f, ndx + 1); + + if (ndx < 0) { + rsync_xa *rxa; + int count = sxp->xattr->count; + write_varint(f, count); + for (rxa = sxp->xattr->items; count--; rxa++) { + size_t name_len = rxa->name_len; + const char *name = rxa->name; + /* Strip the rsync prefix from disguised namespaces. */ + if (name_len > RPRE_LEN +#ifdef HAVE_LINUX_XATTRS + && am_root < 0 +#endif + && name[RPRE_LEN] != '%' && HAS_PREFIX(name, RSYNC_PREFIX)) { + name += RPRE_LEN; + name_len -= RPRE_LEN; + } +#ifndef HAVE_LINUX_XATTRS + else { + /* Put everything else in the user namespace. */ + name_len += UPRE_LEN; + } +#endif + write_varint(f, name_len); + write_varint(f, rxa->datum_len); +#ifndef HAVE_LINUX_XATTRS + if (name_len > rxa->name_len) { + write_buf(f, USER_PREFIX, UPRE_LEN); + name_len -= UPRE_LEN; + } +#endif + write_buf(f, name, name_len); + if (rxa->datum_len > MAX_FULL_DATUM) + write_buf(f, rxa->datum + 1, MAX_DIGEST_LEN); + else + write_bigbuf(f, rxa->datum, rxa->datum_len); + } + ndx = rsync_xal_l.count; /* pre-incremented count */ + rsync_xal_store(sxp->xattr); /* adds item to rsync_xal_l */ + } + + return ndx; +} + +/* Return a flag indicating if we need to change a file's xattrs. If + * "find_all" is specified, also mark any abbreviated xattrs that we + * need so that send_xattr_request() can tell the sender about them. */ +int xattr_diff(struct file_struct *file, stat_x *sxp, int find_all) +{ + item_list *lst = rsync_xal_l.items; + rsync_xa *snd_rxa, *rec_rxa; + int snd_cnt, rec_cnt; + int cmp, same, xattrs_equal = 1; + + if (sxp && XATTR_READY(*sxp)) { + rec_rxa = sxp->xattr->items; + rec_cnt = sxp->xattr->count; + } else { + rec_rxa = NULL; + rec_cnt = 0; + } + + if (F_XATTR(file) >= 0) + lst += F_XATTR(file); + else + lst = &empty_xattr; + + snd_rxa = lst->items; + snd_cnt = lst->count; + + /* If the count of the sender's xattrs is different from our + * (receiver's) xattrs, the lists are not the same. */ + if (snd_cnt != rec_cnt) { + if (!find_all) + return 1; + xattrs_equal = 0; + } + + while (snd_cnt) { + cmp = rec_cnt ? strcmp(snd_rxa->name, rec_rxa->name) : -1; + if (cmp > 0) + same = 0; + else if (snd_rxa->datum_len > MAX_FULL_DATUM) { + same = cmp == 0 && snd_rxa->datum_len == rec_rxa->datum_len + && memcmp(snd_rxa->datum + 1, rec_rxa->datum + 1, + MAX_DIGEST_LEN) == 0; + /* Flag unrequested items that we need. */ + if (!same && find_all && snd_rxa->datum[0] == XSTATE_ABBREV) + snd_rxa->datum[0] = XSTATE_TODO; + } else { + same = cmp == 0 && snd_rxa->datum_len == rec_rxa->datum_len + && memcmp(snd_rxa->datum, rec_rxa->datum, + snd_rxa->datum_len) == 0; + } + if (!same) { + if (!find_all) + return 1; + xattrs_equal = 0; + } + + if (cmp <= 0) { + snd_rxa++; + snd_cnt--; + } + if (cmp >= 0) { + rec_rxa++; + rec_cnt--; + } + } + + if (rec_cnt) + xattrs_equal = 0; + + return !xattrs_equal; +} + +/* When called by the generator (with a NULL fname), this tells the sender + * all the abbreviated xattr values we need. When called by the sender + * (with a non-NULL fname), we send all the extra xattr data it needs. + * The generator may also call with f_out < 0 to just change all the + * XSTATE_ABBREV states into XSTATE_DONE. */ +void send_xattr_request(const char *fname, struct file_struct *file, int f_out) +{ + item_list *lst = rsync_xal_l.items; + int cnt, prior_req = 0; + rsync_xa *rxa; + + lst += F_XATTR(file); + for (rxa = lst->items, cnt = lst->count; cnt--; rxa++) { + if (rxa->datum_len <= MAX_FULL_DATUM) + continue; + switch (rxa->datum[0]) { + case XSTATE_ABBREV: + /* Items left abbreviated matched the sender's checksum, so + * the receiver will cache the local data for future use. */ + if (am_generator) + rxa->datum[0] = XSTATE_DONE; + continue; + case XSTATE_TODO: + assert(f_out >= 0); + break; + default: + continue; + } + + /* Flag that we handled this abbreviated item. */ + rxa->datum[0] = XSTATE_DONE; + + write_varint(f_out, rxa->num - prior_req); + prior_req = rxa->num; + + if (fname) { + size_t len = 0; + char *ptr; + + /* Re-read the long datum. */ + if (!(ptr = get_xattr_data(fname, rxa->name, &len, 0))) { + rprintf(FERROR_XFER, "failed to re-read xattr %s for %s\n", rxa->name, fname); + write_varint(f_out, 0); + continue; + } + + write_varint(f_out, len); /* length might have changed! */ + write_bigbuf(f_out, ptr, len); + free(ptr); + } + } + + if (f_out >= 0) + write_byte(f_out, 0); /* end the list */ +} + +/* When called by the sender, read the request from the generator and mark + * any needed xattrs with a flag that lets us know they need to be sent to + * the receiver. When called by the receiver, reads the sent data and + * stores it in place of its checksum. */ +int recv_xattr_request(struct file_struct *file, int f_in) +{ + item_list *lst = rsync_xal_l.items; + char *old_datum, *name; + rsync_xa *rxa; + int rel_pos, cnt, num, got_xattr_data = 0; + + if (F_XATTR(file) < 0) { + rprintf(FERROR, "recv_xattr_request: internal data error!\n"); + exit_cleanup(RERR_PROTOCOL); + } + lst += F_XATTR(file); + + cnt = lst->count; + rxa = lst->items; + num = 0; + while ((rel_pos = read_varint(f_in)) != 0) { + num += rel_pos; + /* Note that the sender-related num values may not be in order on the receiver! */ + while (cnt && (am_sender ? rxa->num < num : rxa->num != num)) { + rxa++; + cnt--; + } + if (!cnt || rxa->num != num) { + rprintf(FERROR, "[%s] could not find xattr #%d for %s\n", + who_am_i(), num, f_name(file, NULL)); + exit_cleanup(RERR_PROTOCOL); + } + if (!XATTR_ABBREV(*rxa) || rxa->datum[0] != XSTATE_ABBREV) { + rprintf(FERROR, "[%s] internal abbrev error on %s (%s, len=%ld)!\n", + who_am_i(), f_name(file, NULL), rxa->name, (long)rxa->datum_len); + exit_cleanup(RERR_PROTOCOL); + } + + if (am_sender) { + rxa->datum[0] = XSTATE_TODO; + continue; + } + + old_datum = rxa->datum; + rxa->datum_len = read_varint(f_in); + + if (rxa->name_len + rxa->datum_len < rxa->name_len) + overflow_exit("recv_xattr_request"); + rxa->datum = new_array(char, rxa->datum_len + rxa->name_len); + if (!rxa->datum) + out_of_memory("recv_xattr_request"); + name = rxa->datum + rxa->datum_len; + memcpy(name, rxa->name, rxa->name_len); + rxa->name = name; + free(old_datum); + read_buf(f_in, rxa->datum, rxa->datum_len); + got_xattr_data = 1; + } + + return got_xattr_data; +} + +/* ------------------------------------------------------------------------- */ + +/* receive and build the rsync_xattr_lists */ +void receive_xattr(int f, struct file_struct *file) +{ + static item_list temp_xattr = EMPTY_ITEM_LIST; + int count, num; +#ifdef HAVE_LINUX_XATTRS + int need_sort = 0; +#else + int need_sort = 1; +#endif + int ndx = read_varint(f); + + if (ndx < 0 || (size_t)ndx > rsync_xal_l.count) { + rprintf(FERROR, "receive_xattr: xa index %d out of" + " range for %s\n", ndx, f_name(file, NULL)); + exit_cleanup(RERR_STREAMIO); + } + + if (ndx != 0) { + F_XATTR(file) = ndx - 1; + return; + } + + if ((count = read_varint(f)) != 0) { + (void)EXPAND_ITEM_LIST(&temp_xattr, rsync_xa, count); + temp_xattr.count = 0; + } + + for (num = 1; num <= count; num++) { + char *ptr, *name; + rsync_xa *rxa; + size_t name_len = read_varint(f); + size_t datum_len = read_varint(f); + size_t dget_len = datum_len > MAX_FULL_DATUM ? 1 + MAX_DIGEST_LEN : datum_len; + size_t extra_len = MIGHT_NEED_RPRE ? RPRE_LEN : 0; + if ((dget_len + extra_len < dget_len) + || (dget_len + extra_len + name_len < dget_len)) + overflow_exit("receive_xattr"); + ptr = new_array(char, dget_len + extra_len + name_len); + if (!ptr) + out_of_memory("receive_xattr"); + name = ptr + dget_len + extra_len; + read_buf(f, name, name_len); + if (dget_len == datum_len) + read_buf(f, ptr, dget_len); + else { + *ptr = XSTATE_ABBREV; + read_buf(f, ptr + 1, MAX_DIGEST_LEN); + } +#ifdef HAVE_LINUX_XATTRS + /* Non-root can only save the user namespace. */ + if (am_root <= 0 && !HAS_PREFIX(name, USER_PREFIX)) { + if (!am_root) { + free(ptr); + continue; + } + name -= RPRE_LEN; + name_len += RPRE_LEN; + memcpy(name, RSYNC_PREFIX, RPRE_LEN); + need_sort = 1; + } +#else + /* This OS only has a user namespace, so we either + * strip the user prefix, or we put a non-user + * namespace inside our rsync hierarchy. */ + if (HAS_PREFIX(name, USER_PREFIX)) { + name += UPRE_LEN; + name_len -= UPRE_LEN; + } else if (am_root) { + name -= RPRE_LEN; + name_len += RPRE_LEN; + memcpy(name, RSYNC_PREFIX, RPRE_LEN); + } else { + free(ptr); + continue; + } +#endif + /* No rsync.%FOO attributes are copied w/o 2 -X options. */ + if (preserve_xattrs < 2 && name_len > RPRE_LEN + && name[RPRE_LEN] == '%' && HAS_PREFIX(name, RSYNC_PREFIX)) { + free(ptr); + continue; + } + rxa = EXPAND_ITEM_LIST(&temp_xattr, rsync_xa, 1); + rxa->name = name; + rxa->datum = ptr; + rxa->name_len = name_len; + rxa->datum_len = datum_len; + rxa->num = num; + } + + if (need_sort && count > 1) + qsort(temp_xattr.items, count, sizeof (rsync_xa), rsync_xal_compare_names); + + ndx = rsync_xal_l.count; /* pre-incremented count */ + rsync_xal_store(&temp_xattr); /* adds item to rsync_xal_l */ + + F_XATTR(file) = ndx; +} + +/* Turn the xattr data in stat_x into cached xattr data, setting the index + * values in the file struct. */ +void cache_tmp_xattr(struct file_struct *file, stat_x *sxp) +{ + int ndx; + + if (!sxp->xattr) + return; + + if (prior_xattr_count == (size_t)-1) + prior_xattr_count = rsync_xal_l.count; + ndx = find_matching_xattr(sxp->xattr); + if (ndx < 0) + rsync_xal_store(sxp->xattr); /* adds item to rsync_xal_l */ + + F_XATTR(file) = ndx; +} + +void uncache_tmp_xattrs(void) +{ + if (prior_xattr_count != (size_t)-1) { + item_list *xattr_item = rsync_xal_l.items; + item_list *xattr_start = xattr_item + prior_xattr_count; + xattr_item += rsync_xal_l.count; + rsync_xal_l.count = prior_xattr_count; + while (xattr_item-- > xattr_start) + rsync_xal_free(xattr_item); + prior_xattr_count = (size_t)-1; + } +} + +static int rsync_xal_set(const char *fname, item_list *xalp, + const char *fnamecmp, stat_x *sxp) +{ + rsync_xa *rxas = xalp->items; + ssize_t list_len; + size_t i, len; + char *name, *ptr, sum[MAX_DIGEST_LEN]; +#ifdef HAVE_LINUX_XATTRS + int user_only = am_root <= 0; +#endif + size_t name_len; + int ret = 0; + + /* This puts the current name list into the "namebuf" buffer. */ + if ((list_len = get_xattr_names(fname)) < 0) + return -1; + + for (i = 0; i < xalp->count; i++) { + name = rxas[i].name; + + if (XATTR_ABBREV(rxas[i])) { + /* See if the fnamecmp version is identical. */ + len = name_len = rxas[i].name_len; + if ((ptr = get_xattr_data(fnamecmp, name, &len, 1)) == NULL) { + still_abbrev: + if (am_generator) + continue; + rprintf(FERROR, "Missing abbreviated xattr value, %s, for %s\n", + rxas[i].name, full_fname(fname)); + ret = -1; + continue; + } + if (len != rxas[i].datum_len) { + free(ptr); + goto still_abbrev; + } + + sum_init(checksum_seed); + sum_update(ptr, len); + sum_end(sum); + if (memcmp(sum, rxas[i].datum + 1, MAX_DIGEST_LEN) != 0) { + free(ptr); + goto still_abbrev; + } + + if (fname == fnamecmp) + ; /* Value is already set when identical */ + else if (sys_lsetxattr(fname, name, ptr, len) < 0) { + rsyserr(FERROR_XFER, errno, + "rsync_xal_set: lsetxattr(\"%s\",\"%s\") failed", + full_fname(fname), name); + ret = -1; + } else /* make sure caller sets mtime */ + sxp->st.st_mtime = (time_t)-1; + + if (am_generator) { /* generator items stay abbreviated */ + free(ptr); + continue; + } + + memcpy(ptr + len, name, name_len); + free(rxas[i].datum); + + rxas[i].name = name = ptr + len; + rxas[i].datum = ptr; + continue; + } + + if (sys_lsetxattr(fname, name, rxas[i].datum, rxas[i].datum_len) < 0) { + rsyserr(FERROR_XFER, errno, + "rsync_xal_set: lsetxattr(\"%s\",\"%s\") failed", + full_fname(fname), name); + ret = -1; + } else /* make sure caller sets mtime */ + sxp->st.st_mtime = (time_t)-1; + } + + /* Remove any extraneous names. */ + for (name = namebuf; list_len > 0; name += name_len) { + name_len = strlen(name) + 1; + list_len -= name_len; + +#ifdef HAVE_LINUX_XATTRS + /* We always ignore the system namespace, and non-root + * ignores everything but the user namespace. */ + if (user_only ? !HAS_PREFIX(name, USER_PREFIX) + : HAS_PREFIX(name, SYSTEM_PREFIX)) + continue; +#endif + if (am_root < 0 && name_len > RPRE_LEN + && name[RPRE_LEN] == '%' && strcmp(name, XSTAT_ATTR) == 0) + continue; + + for (i = 0; i < xalp->count; i++) { + if (strcmp(name, rxas[i].name) == 0) + break; + } + if (i == xalp->count) { + if (sys_lremovexattr(fname, name) < 0) { + rsyserr(FERROR_XFER, errno, + "rsync_xal_set: lremovexattr(\"%s\",\"%s\") failed", + full_fname(fname), name); + ret = -1; + } else /* make sure caller sets mtime */ + sxp->st.st_mtime = (time_t)-1; + } + } + + return ret; +} + +/* Set extended attributes on indicated filename. */ +int set_xattr(const char *fname, const struct file_struct *file, + const char *fnamecmp, stat_x *sxp) +{ + int ndx; + item_list *lst = rsync_xal_l.items; + + if (dry_run) + return 1; /* FIXME: --dry-run needs to compute this value */ + + if (read_only || list_only) { + errno = EROFS; + return -1; + } + +#ifdef NO_SPECIAL_XATTRS + if (IS_SPECIAL(sxp->st.st_mode)) { + errno = ENOTSUP; + return -1; + } +#endif +#ifdef NO_DEVICE_XATTRS + if (IS_DEVICE(sxp->st.st_mode)) { + errno = ENOTSUP; + return -1; + } +#endif +#ifdef NO_SYMLINK_XATTRS + if (S_ISLNK(sxp->st.st_mode)) { + errno = ENOTSUP; + return -1; + } +#endif + + ndx = F_XATTR(file); + return rsync_xal_set(fname, lst + ndx, fnamecmp, sxp); +} + +#ifdef SUPPORT_ACLS +char *get_xattr_acl(const char *fname, int is_access_acl, size_t *len_p) +{ + const char *name = is_access_acl ? XACC_ACL_ATTR : XDEF_ACL_ATTR; + *len_p = 0; /* no extra data alloc needed from get_xattr_data() */ + return get_xattr_data(fname, name, len_p, 1); +} + +int set_xattr_acl(const char *fname, int is_access_acl, const char *buf, size_t buf_len) +{ + const char *name = is_access_acl ? XACC_ACL_ATTR : XDEF_ACL_ATTR; + if (sys_lsetxattr(fname, name, buf, buf_len) < 0) { + rsyserr(FERROR_XFER, errno, + "set_xattr_acl: lsetxattr(\"%s\",\"%s\") failed", + full_fname(fname), name); + return -1; + } + return 0; +} + +int del_def_xattr_acl(const char *fname) +{ + return sys_lremovexattr(fname, XDEF_ACL_ATTR); +} +#endif + +int get_stat_xattr(const char *fname, int fd, STRUCT_STAT *fst, STRUCT_STAT *xst) +{ + int mode, rdev_major, rdev_minor, uid, gid, len; + char buf[256]; + + if (am_root >= 0 || IS_DEVICE(fst->st_mode) || IS_SPECIAL(fst->st_mode)) + return -1; + + if (xst) + *xst = *fst; + else + xst = fst; + if (fname) { + fd = -1; + len = sys_lgetxattr(fname, XSTAT_ATTR, buf, sizeof buf - 1); + } else { + fname = "fd"; + len = sys_fgetxattr(fd, XSTAT_ATTR, buf, sizeof buf - 1); + } + if (len >= (int)sizeof buf) { + len = -1; + errno = ERANGE; + } + if (len < 0) { + if (errno == ENOTSUP || errno == ENOATTR) + return -1; + if (errno == EPERM && S_ISLNK(fst->st_mode)) { + xst->st_uid = 0; + xst->st_gid = 0; + return 0; + } + rsyserr(FERROR_XFER, errno, "failed to read xattr %s for %s", + XSTAT_ATTR, full_fname(fname)); + return -1; + } + buf[len] = '\0'; + + if (sscanf(buf, "%o %d,%d %d:%d", + &mode, &rdev_major, &rdev_minor, &uid, &gid) != 5) { + rprintf(FERROR, "Corrupt %s xattr attached to %s: \"%s\"\n", + XSTAT_ATTR, full_fname(fname), buf); + exit_cleanup(RERR_FILEIO); + } + + xst->st_mode = from_wire_mode(mode); + xst->st_rdev = MAKEDEV(rdev_major, rdev_minor); + xst->st_uid = uid; + xst->st_gid = gid; + + return 0; +} + +int set_stat_xattr(const char *fname, struct file_struct *file, mode_t new_mode) +{ + STRUCT_STAT fst, xst; + dev_t rdev; + mode_t mode, fmode; + + if (dry_run) + return 0; + + if (read_only || list_only) { + rsyserr(FERROR_XFER, EROFS, "failed to write xattr %s for %s", + XSTAT_ATTR, full_fname(fname)); + return -1; + } + + if (x_lstat(fname, &fst, &xst) < 0) { + rsyserr(FERROR_XFER, errno, "failed to re-stat %s", + full_fname(fname)); + return -1; + } + + fst.st_mode &= (_S_IFMT | CHMOD_BITS); + fmode = new_mode & (_S_IFMT | CHMOD_BITS); + + if (IS_DEVICE(fmode)) { + uint32 *devp = F_RDEV_P(file); + rdev = MAKEDEV(DEV_MAJOR(devp), DEV_MINOR(devp)); + } else + rdev = 0; + + /* Dump the special permissions and enable full owner access. */ + mode = (fst.st_mode & _S_IFMT) | (fmode & ACCESSPERMS) + | (S_ISDIR(fst.st_mode) ? 0700 : 0600); + if (fst.st_mode != mode) + do_chmod(fname, mode); + if (!IS_DEVICE(fst.st_mode)) + fst.st_rdev = 0; /* just in case */ + + if (mode == fmode && fst.st_rdev == rdev + && fst.st_uid == F_OWNER(file) && fst.st_gid == F_GROUP(file)) { + /* xst.st_mode will be 0 if there's no current stat xattr */ + if (xst.st_mode && sys_lremovexattr(fname, XSTAT_ATTR) < 0) { + rsyserr(FERROR_XFER, errno, + "delete of stat xattr failed for %s", + full_fname(fname)); + return -1; + } + return 0; + } + + if (xst.st_mode != fmode || xst.st_rdev != rdev + || xst.st_uid != F_OWNER(file) || xst.st_gid != F_GROUP(file)) { + char buf[256]; + int len = snprintf(buf, sizeof buf, "%o %u,%u %u:%u", + to_wire_mode(fmode), + (int)major(rdev), (int)minor(rdev), + F_OWNER(file), F_GROUP(file)); + if (sys_lsetxattr(fname, XSTAT_ATTR, buf, len) < 0) { + if (errno == EPERM && S_ISLNK(fst.st_mode)) + return 0; + rsyserr(FERROR_XFER, errno, + "failed to write xattr %s for %s", + XSTAT_ATTR, full_fname(fname)); + return -1; + } + } + + return 0; +} + +int x_stat(const char *fname, STRUCT_STAT *fst, STRUCT_STAT *xst) +{ + int ret = do_stat(fname, fst); + if ((ret < 0 || get_stat_xattr(fname, -1, fst, xst) < 0) && xst) + xst->st_mode = 0; + return ret; +} + +int x_lstat(const char *fname, STRUCT_STAT *fst, STRUCT_STAT *xst) +{ + int ret = do_lstat(fname, fst); + if ((ret < 0 || get_stat_xattr(fname, -1, fst, xst) < 0) && xst) + xst->st_mode = 0; + return ret; +} + +int x_fstat(int fd, STRUCT_STAT *fst, STRUCT_STAT *xst) +{ + int ret = do_fstat(fd, fst); + if ((ret < 0 || get_stat_xattr(NULL, fd, fst, xst) < 0) && xst) + xst->st_mode = 0; + return ret; +} + +#endif /* SUPPORT_XATTRS */