Project import
diff --git a/logrotate/.travis.yml b/logrotate/.travis.yml new file mode 100644 index 0000000..c1696d9 --- /dev/null +++ b/logrotate/.travis.yml
@@ -0,0 +1,128 @@ +sudo: required +dist: precise +language: c + +matrix: + include: + - compiler: gcc + addons: + apt: + sources: + - ubuntu-toolchain-r-test + packages: + - gcc-4.4 + env: COMPILER=gcc-4.4 + - compiler: gcc + addons: + apt: + sources: + - ubuntu-toolchain-r-test + packages: + - gcc-4.5 + env: COMPILER=gcc-4.5 + - compiler: gcc + addons: + apt: + sources: + - ubuntu-toolchain-r-test + packages: + - gcc-4.6 + env: COMPILER=gcc-4.6 + - compiler: gcc + addons: + apt: + sources: + - ubuntu-toolchain-r-test + packages: + - gcc-4.7 + env: COMPILER=gcc-4.7 + - compiler: gcc + addons: + apt: + sources: + - ubuntu-toolchain-r-test + packages: + - gcc-4.8 + env: COMPILER=gcc-4.8 + - compiler: gcc + addons: + apt: + sources: + - ubuntu-toolchain-r-test + packages: + - gcc-4.9 + env: COMPILER=gcc-4.9 + - compiler: gcc + addons: + apt: + sources: + - ubuntu-toolchain-r-test + packages: + - gcc-5 + env: COMPILER=gcc-5 + - compiler: gcc + addons: + apt: + sources: + - ubuntu-toolchain-r-test + packages: + - gcc-6 + env: COMPILER=gcc-6 + - compiler: clang + addons: + apt: + packages: + - clang-3.4 + env: COMPILER=clang + - compiler: clang + addons: + apt: + sources: + - ubuntu-toolchain-r-test + - llvm-toolchain-precise-3.5 + packages: + - clang-3.5 + env: COMPILER=clang-3.5 + - compiler: clang + addons: + apt: + sources: + - ubuntu-toolchain-r-test + - llvm-toolchain-precise-3.6 + packages: + - clang-3.6 + env: COMPILER=clang-3.6 + - compiler: clang + addons: + apt: + sources: + - ubuntu-toolchain-r-test + - llvm-toolchain-precise-3.7 + packages: + - clang-3.7 + env: COMPILER=clang-3.7 + +install: + - sudo add-apt-repository ppa:dns/gnu -y + - sudo apt-get -qq update + - sudo apt-get -qq install libpopt-dev libselinux1-dev libacl1-dev automake dash rpm + +script: + - $COMPILER --version + - ./autogen.sh + - ./configure CC=$COMPILER + - make + - make test + - cd test && LOGROTATE=../logrotate /bin/dash test && cd .. + - make distcheck + # nodeps because rpm build deps can not be installed on debian system + - make rpm RPM_FLAGS="--nodeps" + +notifications: + email: + recipients: + - logrotate-owner@fedoraproject.org + on_success: always + on_failure: always + +# vim:et:ts=2:sw=2
diff --git a/logrotate/Android.mk b/logrotate/Android.mk new file mode 100644 index 0000000..2bad090 --- /dev/null +++ b/logrotate/Android.mk
@@ -0,0 +1,58 @@ +LOCAL_PATH :=$(call my-dir) +include $(CLEAR_VARS) +LOCAL_MODULE := logrotate +LOCAL_INIT_RC := logrotate.rc +LOCAL_SRC_FILES := basenames.c config.c log.c logrotate.c glob.c +LOCAL_CFLAGS := \ + -D_SIZE_T_DECLARED \ + -DPACKAGE_NAME=\"logrotate\" \ + -DPACKAGE_TARNAME=\"logrotate\" \ + -DPACKAGE_VERSION=\"3.10.0\" \ + -DPACKAGE_STRING=\"logrotate\ 3.10.0\" \ + -DPACKAGE_BUGREPORT=\"\" \ + -DPACKAGE_URL=\"\" \ + -DPACKAGE=\"logrotate\" \ + -DVERSION=\"3.10.0\" \ + -D_GNU_SOURCE=1 \ + -DSTDC_HEADERS=1 \ + -DHAVE_SYS_TYPES_H=1 \ + -DHAVE_SYS_STAT_H=1 \ + -DHAVE_STDLIB_H=1 \ + -DHAVE_STRING_H=1 \ + -DHAVE_MEMORY_H=1 \ + -DHAVE_STRINGS_H=1 \ + -DHAVE_INTTYPES_H=1 \ + -DHAVE_STDINT_H=1 \ + -DHAVE_UNISTD_H=1 \ + -DHAVE_STRUCT_STAT_ST_BLKSIZE=1 \ + -DHAVE_ST_BLKSIZE=1 \ + -DHAVE_STRUCT_STAT_ST_BLOCKS=1 \ + -DHAVE_ST_BLOCKS=1 \ + -DHAVE_LIBPOPT=1 \ + -DHAVE_ASPRINTF=1 \ + -DHAVE_FORK=1 \ + -DHAVE_MADVISE=1 \ + -DHAVE_QSORT=1 \ + -DHAVE_STRNDUP=1 \ + -DHAVE_STRPTIME=1 \ + -DHAVE_VFORK=1 \ + -DHAVE_VSYSLOG=1 \ + +LOCAL_SHARED_LIBRARIES := \ + libpopt \ + liblog \ + libcutils \ + +include $(BUILD_EXECUTABLE) + +include $(CLEAR_VARS) +LOCAL_MODULE := logrotate.conf +LOCAL_SRC_FILES := logrotate.conf +LOCAL_MODULE_CLASS := ETC +include $(BUILD_PREBUILT) + +include $(CLEAR_VARS) +LOCAL_MODULE := log_sender +LOCAL_SRC_FILES := log_sender +LOCAL_MODULE_CLASS := EXECUTABLES +include $(BUILD_PREBUILT)
diff --git a/logrotate/CONTRIBUTING.md b/logrotate/CONTRIBUTING.md new file mode 100644 index 0000000..84d3d52 --- /dev/null +++ b/logrotate/CONTRIBUTING.md
@@ -0,0 +1,16 @@ +# logrotate contributions + +## Pull requests + + - Fork it. + - Create your feature branch (`git checkout -b fixing-blah`), please avoid working directly on the `master` branch. + - Check for unnecessary whitespaces with `git diff --check` before committing. + - [optional] Add user visible changes to `ChangeLog.md` under the `UNRELEASED` section + - Commit your changes, try to follow this format: +``` +scope: short summary of the change +[empty line] +Long description of the change, explanation of why the change is useful, etc. +``` + - Push to the branch (`git push -u origin fixing-blah`). + - Create a new pull request.
diff --git a/logrotate/COPYING b/logrotate/COPYING new file mode 100644 index 0000000..e77696a --- /dev/null +++ b/logrotate/COPYING
@@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 675 Mass Ave, Cambridge, MA 02139, USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) 19yy <name of author> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 19yy name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License.
diff --git a/logrotate/ChangeLog.md b/logrotate/ChangeLog.md new file mode 100644 index 0000000..ad84459 --- /dev/null +++ b/logrotate/ChangeLog.md
@@ -0,0 +1,426 @@ +# logrotate change log + +All notable changes to this project will be documented in this file. + +## [UNRELEASED] + +## [3.10.0] - 2016-08-03 + + - Legacy Makefile renamed to Makefile.legacy, will be removed eventually. + - Fix 'make dist' and 'make distcheck' to produce a usable release tarball. + - Fix 'olddir' usage with wildcard in the middle of path in the pattern + definition when the pattern did not match any log file. + - Remove half-rotated files when rotation of particular log file is skipped + because of an error during copy or compression. + +## [3.9.2] - 2016-01-20 + - Upstream moved to GitHub: <https://github.com/logrotate/logrotate>. + - Add support for %M, %S and %V in "dateext" directive. + - Fix bad filename in subject of email when "compress" and "maillast" is + used. + - Allow rotating files created before 1996. + - Fix compilation errors on NetBSD caused by "array subscript has + type 'char' in config.c" + - Fix matching subdirectories on BSD systems for patterns like + "*/log" in situation where logrotate tried to match "foo/log" even when + "foo" has not been a directory. + - Fix logging dates in debug messages. + - Remove state file entries for logs which do not exist and have not been + rotated for more than a year. + - Fix poor performance with big state file. + - Support logging to syslog by using '-l syslog'. + - Allow running test-suite using dash. + +## [3.9.1] - 2015-04-03 + - Fix off-by-one error which can lead to crash when copytruncate is used. + +## [3.9.0] - 2015-04-03 + - Fix crash when using long dateformat. [nmerdan] + - Add support for %H dateformat. [czchen] + - Fix regression introduced in 3.8.9 when when rotating multiple + logs when one of them is missing. + - In the debug mode, do not skip the code-path which handles the case when + the last rotation does not exist. [Sergey Vidishev] + - Show more precise description when "log does not need rotating". + - Add new -l option to log verbose output to file. The file is overwritten + on every logrotate execution. + - Allow rotation of sparse files with copytruncate. + +## [3.8.9] - 2015-02-13 + - Add new directive "createolddir" and "nocreateolddir". These directives + can be used to create the directory specified by olddir with particular + "mode", "owner" and "group". + - Continue with rotation even when first log from logset is removed + during the rotation. + - Fix crash on BSD systems introduced in 3.8.8 caused by different qsort_r + function. Function qsort is now used instead. + - Fix potential buffer overflow in usage of strncat function. + - Fix compilation with musl-libc. + - Add experimental 'renamecopy' directive to allow 'olddir' on different + physical device. See the "man logrotate" for more information. + +## [3.8.8] - 2014-10-16 + - Add support for building using autotools/automake. Using "./autogen.sh", + "./configure" and "make" is now preferred way how to build logrotate. + Old Makefile remains available, but it is deprecated and will be removed + in the future. Please report any problem related to new build system. + - Add support for systems which do not support fork (use vfork instead) + and madvise. + - Fix bug when wrong log file has been removed in case of dateext and + dateformat %d-%m-%Y. + - Do not expect that the name of root account is 'root'. + - Do not stop rotation with an error when olddir and log file + are on different devices and copy or copytruncate is used. + - Return an error code when parent directory of log does not exist, + "su" directive is not used, logrotate is running as root and missingok + is not specified. [vcizek] + - Prepend error printed by compression program with the log name even when + the compression program exits with zero exit code. + +## [3.8.7] - 2013-10-10 + - Fixed --force/-f option handling together with "size" directive + (3.8.5 regression). + - Use "logrotate_tmp_t" context for SELinux tests and if this context does + not exist, skip SELinux related tests. + +## [3.8.6] - 2013-07-31 + - Fixed memory corruption caused by rotation directory which does not + exist with "sharedscripts" together with "prerotate" script. + +## [3.8.5] - 2013-06-10 + - Improved rotation during daylight saving time and between timezone + changes. + - Fixed ACL setting problem caused by ext3 erroneously reporting ENOSYS + instead of ENOSUP. + - Do not continue with rotation if state file is corrupted. + - Make logrotate.status creation atomic. + - Allow "hourly" rotation. See manpage for more information. + - Use "/bin/echo" in tests. Fixes tests execution in Dash. + - Do no try to parse config files bigger than 16MB. + - Improved manpage consistency and formatting. + - Fix race condition between acl_set_fd() and fchmod(). + +## [3.8.4] - 2013-04-30 + - Added --version command line option + - Disable ACL tests if logrotate is not compiled WITH_ACL support or if + ACLs are not supported by the system running tests + - Disable SELinux tests if logrotate is not compiled WITH_SELINUX support + or if SELinux is not supported by the system running tests + - Fixed bug which prevented skipping particular log file config + if the config contained errors. + - Fixed skipping of configs containing firstaction/lastaction scripts + with '}' character in case of error before these scripts. + - Support also 'K' unit for *size directives. + - Added preremove option to let admin to do something with the old logs + before they are removed by logrotate. + - Fixed possible loop in tabooext parsing. + - Move code to set SELinux context before compressLogFile calls to create + compressed log files with the proper context. + - Call prerotate/postrotate script only for really rotated files in + nosharedscripts mode (as stated in man page). + +## [3.8.3] - 2012-10-04 + - Fixed setting "size" bigger than 4GB on 32bit architectures + - Do not overwrite mode set by "create" option when using ACL. "create" + directive is now not mixed up with ACLs. If you use "create" in config + file and log file has some ACLs set, ACLs are not kept and are + overwritten by the mode set in "create" directive. + - Mode argument in "create" directive can be omitted. Only owner and group + is set in this case. Check man page for more info. + +## [3.8.2] - 2012-08-01 + - show error and ignore config if '{' is not present after log files + declaration + - support whitespaces in compressoptions directive + - support for tilde expansion in config files + - 'su' directive does not affect script execution - scripts + are executed as a root if 'su' directive is present + - fixed mail sending for 'mailfirst', 'dateext' and 'delaycompress' + combination + - do not use gzip/gunzip from /usr/local on Solaris + - add O_NOFOLLOW when opening files as safeguard against symlink tricks. + Symlinks rotation is now officially unsupported. It didn't work + as expected in the past anyway. + - do not run external programs with uid != euid + - fixed potential bad-free when ACL is used + - Do not include alloca.h on NetBSD, since alloca() is declared in + stdlib.h there + - 13 new tests added + +## [3.8.1] - 2011-08-31 + - fixed 1 memory leak in prerotateSingleLog + - another fixes for Solaris + - fixed HP-UX compilation and default config + - do not redirect logrotate errors to /dev/null in cron script + - fixed "size" directive parsing + - handle situation when acl_get_fd is supported, but acl_set_fd is not + - added "maxsize" directive (see man page) + +## [3.8.0] - 2011-06-21 + - added "dateyesterday" option (see man page) + - fixed crash when config file had exactly 4096*N bytes + - added WITH_ACL make option to link against -lacl and preserve ACLs + during rotation + - added "su" option to define user/group for rotation. Logrotate now + skips directories which are world writable or writable by group + which is not "root" unless "su" directive is used. + - fixed CVE-2011-1098: race condition by creation of new files + - fixed possible shell injection when using "shred" directive (CVE-2011-1154) + - fixed escaping of file names within 'write state' action (CVE-2011-1155) + - better 'size' directive description + - fixed possible buffer-overflow when reading config files + - NetBSD/FreeBSD compilation fixes + - Solaris compilation fixes + +## [3.7.9] - 2010-06-28 + - fix building on Solaris (patch by András Szilárd) + - don't copy config files on the stack -- mmap them instead + (fixes segfaults with too large/invalid config files) + - symlinked conf file man page as requested by Fedora guidelines + (thanks to Ivana Hutarova Varekova) + - cron script logrotate.cron redirects output to /dev/null + - added rotating (copying) non-writable, readable files + (patch by Henrique Martins) + - fixed missingok problem with globs + (taken from the Debian patches by Ted Percival + <ted@midg3t.net>) + - fixed bug when log files could be removed even there was + some error in rotation process. + - allow setting size greater than 4.2GB in configuration file + - pass currently rotated file to postrotate/prerotate script + in nosharedscripts mode + - added new TabooExts: ".disabled", ".dpkg-old", ".dpkg-dist", + ".dpkg-new", ".cfsaved", ".ucf-old", ".ucf-dist", ".ucf-new" + (taken from the Debian patches by Paul Martin <pm@debian.org>) + - Don't change utime atime/mtime when compressing files + (taken from the Debian patches by Paul Martin <pm@debian.org>) + - Better *rotate scripts parser. (taken from the Debian patches) + - Allow 'include' directive in log file definitions + +## [3.7.8] - 2009-01-28 + - do not exit on status file errors + - limit config file inclusion nesting + - use hashes for status file handling (patch by Petr Tesarik + <ptesarik@suse.cz> and Leonardo Chiquitto) + - dateformat to allow unixtime (patch by Sami Kerola + <kerolasa@iki.fi>) + - manual page corrections (taken from the Debian patches by + Paul Martin <pm@debian.org>) + +## [3.7.7] - 2008-05-19 + - dateformat + - fix possible buffer overflows in strings handling + - various minor bugfixes + - change logInfo handling (patches by Leonardo Chiquitto) + +## [3.7.6] - 2008-05-14 + - patches from Leonardo Chiquitto that fix compile warnings + - examples/logrotate-default: add btmp rotation, dateext + - update man page + - tabooext honor wildcards + - fix selinux support with dateext + +## [3.7.5] - 2007-03-01 + - import Fedora patches + - add option to use shred for deleting files, patch by + Peter Eckersley <pde@eff.org> + - ignore .cfsaved files + - bugfixes + +## [3.7.1] - 2004-10-20 + - Fix sending mails and running scripts after the + system() -> execve() changes + - Preserve file attributes when compressing files (original patch + by Daniel Himler) + +## [3.7] - 2004-01-26 + - always use compressext for the extension for compressed + files; before compresscmd and compressext had to agree + - moved all compression to one code block + - compression, scripts don't use system() anymore + - compress and maillast didn't work together properly + - delaycompress and mailfirst didn't work properly + - don't use system() for mailing (or uncompressing) logs anymore + - use "-s" for speciying the subjected of mailed logs + +## [3.6] - 2001-11-28 + - See .spec file for changes + +## [3.5.4] - 2001-01-05 + - %defattr(-,root,root) in specfile + +## [3.5.3] - 2001-01-03 + - patch /tmp file race condition problem, use mkstemp; + Thanks go to Solar Designer <solar@openwall.com> + +## [3.5.2] - 2000-09-29 + - added .swp and .rpmnew to default taboo list + +## [3.5.1] - 2000-08-11 + - handle state dates in the future a bit more sanely + +## [3.5] - 2000-07-23 + - multiple file names/patterns may be given for a single entry + - fixed mistake in when logs were uncompressed before mailing + +## [3.4] - 2000-07-13 + - added sharedscripts/nosharedscripts + - added simple testbed + - quote filenames in state file to allow proper rotation of files + with spaces in the name -- this changes the version number of + the state file! + - ignore white space at end of line + +## [3.3.2] - 2000-06-19 + - don't rotate lastlog + +## [3.3.1] - 2000-02-03 + - support gzipped man pages + +## [3.3] - 1999-06-16 + - added "mailfirst" and "maillast" flags (based on Tim Wall's patch) + - documented "extension" flag + - "rotate 0" gives proper script and mail behavior + +## [3.2] - 1999-04-07 + - create wtmp with correct perms + +## [3.1] - 1999-04-01 + - fixed small alloca() + - added missingok flag + - use popt to display usage message + - handle /some/file { } in config file + +## [3.0] - 1999-03-18 + - updates for glibc 2.1 + +## [2.9] - 1999-03-05 + - fixed a bug parsing lines where { immediately follows the filename + - allow log file patterns to be placed in double quotes, which + allows spaces in names + - complain about missing log files (John Van Essen) + +## [2.8] - 1999-01-13 + - changes for glibc 2.1 (Cristian Gafton) + +## [2.7] - 1998-12-29 + - updated man page to include --force (Simon Mudd) + - invoke scripts via /bin/sh rather then relying on /tmp execute + semantics (Philip Guenther) + - added "extension" option for forcing a file extension after rotation + (Rob Hagopian) + +## [2.6] - 1998-05-05 + - added nodelaycompress flag (from Jos Vos) + - added copytruncate, nocopytruncate flag (from Jos Vos) + - removed umask handling; explicitly use fchmod() insteadmoved umask + - added --force option (Simon Mudd) + - moved /bin/mail to MAIL_COMMAND define (Simon Mudd) + - fixed segv caused by overly long filenames + - switched from getopt_long to popt + +## [2.5] - 1997-09-01 + - set the umask of the process to 0, letting open() create processes + with the proper permissions + - added delaycompress flag (from Jos Vos) + - fixed how old logs are finally removed when an olddir is specified + (Jos Vos) + - added nomail option + - added mail, nomail documentation to man page + - added the tabooext directive + - fixed problem in globbing + +## [2.4] - 1997-08-11 + - glob log names in config file + - added ,v to taboo list + - fixed bug w/ create parsing + - use an int rather then a mode_t when parsing create entries as + sscanf requires it + +## [2.3] - 1997-03-18 + - fill in all of last rotated structure (this probable isn't + really necessary but it's a bit cleaner and will avoid future + problems); + - fixed .spec file + +## [2.2] - 1997-02-27 + - If a file is rotated and we have no state information for it, + right out the current time. + - Weekly rotation happens when the current weekday is less then + the weekday of the last rotation or more then a week has + elapsed between the last rotation and now + - Monthly rotation happens when the current month is different + from the last month or the current year is different from the + last year + - (these were contributed and suggested by Ronald Wahl) + - added olddir/noolddir options + - added ifempty/notifempty options + - ignore nonnormal files when reading config files from a directory + - (these were suggested and originally implemented by + Henning Schmiedehausen) + - updated the man page to reflect these changes + - made "make install" accept PREFIX argument + - added .spec file to tarball + +## [2.1] - 1997-01-13 + - Don't output state information for logs that have never been + rotated (better then 1900-1-0) + - Accept 1900-1-0 as time 0 + +## [2.0.2] - 1996-12-10 + - I have no idea :-( + +## [2.0.1] - 1996-12-09 + - ignore files in included directories which end with ~, .rpmorig, or + .rpmsave + +[UNRELEASED]: https://github.com/logrotate/logrotate/compare/3.10.0...master +[3.10.0]: https://github.com/logrotate/logrotate/compare/3.9.2...3.10.0 + [3.9.2]: https://github.com/logrotate/logrotate/compare/r3-9-1...3.9.2 + [3.9.1]: https://github.com/logrotate/logrotate/compare/r3-9-0...r3-9-1 + [3.9.0]: https://github.com/logrotate/logrotate/compare/r3-8-9...r3-9-0 + [3.8.9]: https://github.com/logrotate/logrotate/compare/r3-8-8...r3-8-9 + [3.8.8]: https://github.com/logrotate/logrotate/compare/r3-8-7...r3-8-8 + [3.8.7]: https://github.com/logrotate/logrotate/compare/r3-8-6...r3-8-7 + [3.8.6]: https://github.com/logrotate/logrotate/compare/r3-8-5...r3-8-6 + [3.8.5]: https://github.com/logrotate/logrotate/compare/r3-8-4...r3-8-5 + [3.8.4]: https://github.com/logrotate/logrotate/compare/r3-8-3...r3-8-4 + [3.8.3]: https://github.com/logrotate/logrotate/compare/r3.8.2...r3-8-3 + [3.8.2]: https://github.com/logrotate/logrotate/compare/r3-8-1...r3.8.2 + [3.8.1]: https://github.com/logrotate/logrotate/compare/r3-8-0...r3-8-1 + [3.8.0]: https://github.com/logrotate/logrotate/compare/r3-7-9...r3-8-0 + [3.7.9]: https://github.com/logrotate/logrotate/compare/r3-7-8...r3-7-9 + [3.7.8]: https://github.com/logrotate/logrotate/compare/r3-7-7...r3-7-8 + [3.7.7]: https://github.com/logrotate/logrotate/compare/r3-7-6...r3-7-7 + [3.7.6]: https://github.com/logrotate/logrotate/compare/r3-7-5...r3-7-6 + [3.7.5]: https://github.com/logrotate/logrotate/compare/r3-7-1...r3-7-5 + [3.7.1]: https://github.com/logrotate/logrotate/compare/r3-7...r3-7-1 + [3.7]: https://github.com/logrotate/logrotate/compare/r3-6...r3-7 + [3.6]: https://github.com/logrotate/logrotate/compare/r3-5-4...r3-6 + [3.5.4]: https://github.com/logrotate/logrotate/compare/r3-5-3...r3-5-4 + [3.5.3]: https://github.com/logrotate/logrotate/compare/r3-5-2...r3-5-3 + [3.5.2]: https://github.com/logrotate/logrotate/compare/r3-5-1...r3-5-2 + [3.5.1]: https://github.com/logrotate/logrotate/compare/r3-5...r3-5-1 + [3.5]: https://github.com/logrotate/logrotate/compare/r3-4...r3-5 + [3.4]: https://github.com/logrotate/logrotate/compare/r3-3-2...r3-4 + [3.3.2]: https://github.com/logrotate/logrotate/compare/r3-3-1...r3-3-2 + [3.3.1]: https://github.com/logrotate/logrotate/compare/r3-3...r3-3-1 + [3.3]: https://github.com/logrotate/logrotate/compare/r3-2...r3-3 + [3.2]: https://github.com/logrotate/logrotate/compare/r3-1...r3-2 + [3.1]: https://github.com/logrotate/logrotate/compare/r3-0...r3-1 + [3.0]: https://github.com/logrotate/logrotate/compare/r2-9...r3-0 + [2.9]: https://github.com/logrotate/logrotate/compare/r2-8...r2-9 + [2.8]: https://github.com/logrotate/logrotate/compare/r2-7...r2-8 + [2.7]: https://github.com/logrotate/logrotate/compare/r2-6...r2-7 + [2.6]: https://github.com/logrotate/logrotate/compare/r2-5...r2-6 + [2.5]: https://github.com/logrotate/logrotate/compare/r2-4...r2-5 + [2.4]: https://github.com/logrotate/logrotate/compare/2-3...r2-4 + [2.3]: https://github.com/logrotate/logrotate/compare/2-2...2-3 + [2.2]: https://github.com/logrotate/logrotate/compare/2-1...2-2 + [2.1]: https://github.com/logrotate/logrotate/compare/2-0-2...2-1 + [2.0.2]: https://github.com/logrotate/logrotate/compare/2-0-1...2-0-2 + [2.0.1]: https://github.com/logrotate/logrotate/commits/2-0-1 + +<!-- +vim:et:sw=2:ts=2 +-->
diff --git a/logrotate/INSTALL b/logrotate/INSTALL new file mode 100644 index 0000000..79fc3b5 --- /dev/null +++ b/logrotate/INSTALL
@@ -0,0 +1,23 @@ +The simplest way to compile this package is: + + 1. `cd' to the directory containing the package's source code + + 2. Configure logrotate using `./configure'. + + 3. Type `make' to compile the package. + + 4. Optionally, type `make check' to run any self-tests that come with + the package. + + 5. Type `make install' to install the programs and any data files and + documentation. + + 6. You can remove the program binaries and object files from the + source code directory by typing `make clean'. + + +If you want to add the Access Control List (ACL) support to the program +use `./configure --with-acl=yes' at the point 2. + +If you want to add the NSA Security-Enhanced Linux (SELinux) support to +the program use `./configure --with-selinux=yes' at the point 2.
diff --git a/logrotate/LICENSE b/logrotate/LICENSE new file mode 100644 index 0000000..e77696a --- /dev/null +++ b/logrotate/LICENSE
@@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 675 Mass Ave, Cambridge, MA 02139, USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) 19yy <name of author> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 19yy name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License.
diff --git a/logrotate/MODULE_LICENSE_GPL b/logrotate/MODULE_LICENSE_GPL new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/logrotate/MODULE_LICENSE_GPL
diff --git a/logrotate/Makefile.am b/logrotate/Makefile.am new file mode 100644 index 0000000..da3f21b --- /dev/null +++ b/logrotate/Makefile.am
@@ -0,0 +1,36 @@ +# +# 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, version 2 of the License. +# +# 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. +# +AM_CFLAGS = -Wall -Werror +sbin_PROGRAMS = logrotate +logrotate_SOURCES = basenames.c config.c log.c logrotate.c \ + basenames.h config.h log.h logrotate.h queue.h + +dist_man_MANS = logrotate.8 logrotate.conf.5 + +dist_noinst_DATA = logrotate.spec + +EXTRA_DIST = ChangeLog.md README.md autogen.sh examples + +# deprecated hard-wired Makefile, will be removed eventually +EXTRA_DIST += Makefile.legacy + +# the dot ensures that logrotate is built before it is tested +SUBDIRS = . test + +# for compatibility with older releases of logrotate +test: check + +.PHONY: srpm rpm + +rpm: srpm + rpmbuild $(RPM_FLAGS) -ta $(distdir).tar.gz +srpm: dist + rpmbuild $(RPM_FLAGS) -ts $(distdir).tar.gz
diff --git a/logrotate/Makefile.legacy b/logrotate/Makefile.legacy new file mode 100644 index 0000000..7aa5b20 --- /dev/null +++ b/logrotate/Makefile.legacy
@@ -0,0 +1,189 @@ +VERSION = $(shell awk '/Version:/ { print $$2 }' logrotate.spec) +OS_NAME = $(shell uname -s) +LFS = $(shell echo `getconf LFS_CFLAGS 2>/dev/null`) +CFLAGS = -Wall -D_GNU_SOURCE -D$(OS_NAME) -DVERSION=\"$(VERSION)\" -DHAVE_STRPTIME=1 -DHAVE_QSORT -DHAVE_STRUCT_STAT_ST_BLOCKS -DHAVE_STRUCT_STAT_ST_BLKSIZE -DHAVE_VSYSLOG $(RPM_OPT_FLAGS) $(LFS) +PROG = logrotate +MAN = logrotate.8 +MAN5 = logrotate.conf.5 +LOADLIBES = -lpopt +SVNURL= svn+ssh://svn.fedorahosted.org/svn/logrotate +SVNPUBURL = http://svn.fedorahosted.org/svn/logrotate +SVNTAG = r$(subst .,-,$(VERSION)) + +ifeq ($(WITH_SELINUX),yes) +CFLAGS += -DWITH_SELINUX +LOADLIBES += -lselinux +# See pretest +TEST_SELINUX=1 +else +# See pretest +TEST_SELINUX=0 +endif + +ifeq ($(WITH_ACL),yes) +CFLAGS += -DWITH_ACL +LOADLIBES += -lacl +# See pretest +TEST_ACL=1 +else +# See pretest +TEST_ACL=0 +endif + +# HP-UX using GCC +ifeq ($(OS_NAME),HP-UX) + ifeq ($(RPM_OPT_FLAGS),) + RPM_OPT_FLAGS = -O2 + endif + CC = gcc + INSTALL = cpset + ifeq ($(POPT_DIR),) + POPT_DIR = /usr/local + endif + ifeq ($(HPLX_DIR),) + HPLX_DIR = /usr/local/hplx + endif + LOADLIBES += -lhplx -L$(HPLX_DIR)/lib + ifeq ($(BASEDIR),) + BASEDIR = /usr/local + endif +endif + +# Solaris using gcc +ifeq ($(OS_NAME),SunOS) + CFLAGS = -D_GNU_SOURCE -D$(OS_NAME) -DVERSION=\"$(VERSION)\" $(RPM_OPT_FLAGS) $(LFS) + CC ?= gcc + CPP = $(CC) -E -M + INSTALL = /usr/ucb/install + ifeq ($(CC),cc) + CPP = cc -xM + endif + BASEDIR ?= /usr/local +endif + +# Red Hat Linux +ifeq ($(OS_NAME),Linux) + INSTALL = install + BASEDIR = /usr +endif + +# FreeBSD +ifeq ($(OS_NAME),FreeBSD) + LOADLIBES += -L${LOCALBASE}/lib + CFLAGS += -I${LOCALBASE}/include + PREFIX= +endif + +ifeq ($(OS_NAME),NetBSD) + CFLAGS += -I/usr/include + CFLAGS += -I$(BASEDIR)/include + LOADLIBES += -L/usr/lib + LOADLIBES += -L$(BASEDIR)/lib -Wl,-R,$(BASEDIR)/lib +endif + +ifneq ($(POPT_DIR),) + CFLAGS += -I$(POPT_DIR) + LOADLIBES += -L$(POPT_DIR) +endif + +ifneq ($(STATEFILE),) + CFLAGS += -DSTATEFILE=\"$(STATEFILE)\" +endif + +BINDIR = $(BASEDIR)/sbin +MANDIR ?= $(BASEDIR)/man + +#-------------------------------------------------------------------------- + +OBJS = logrotate.o log.o config.o basenames.o +SOURCES = $(subst .o,.c,$(OBJS) $(LIBOBJS)) + +ifeq ($(RPM_OPT_FLAGS),) +CFLAGS += -g +LDFLAGS = -g +endif + +LDFLAGS += $(EXTRA_LDFLAGS) $(EXTRA_LIBS) +CFLAGS += $(EXTRA_CPPFLAGS) $(EXTRA_CFLAGS) + +ifeq (.depend,$(wildcard .depend)) +TARGET=$(PROG) +else +TARGET=depend $(PROG) +endif + +RCSVERSION = $(subst .,-,$(VERSION)) + +all: show_warning $(TARGET) pretest + +show_warning: + @echo "" + @echo "Building using this Makefile is DEPRECATED." + @echo "Use './autogen.sh', './configure' and 'make' instead." + @echo "Some new features will not be enabled when building using this Makefile." + @echo "" + +$(PROG): $(OBJS) + +clean: + rm -f $(OBJS) $(PROG) core* .depend + rm -f ./test/test.ACL ./test/test.SELINUX ./test/error.log + +depend: + $(CPP) $(CFLAGS) -M $(SOURCES) > .depend + +# pretest create the file ./test/test.ACL with +# 0 or 1 according to the WITH_ACL=yes presence. +# The file will be used by ./test/test to decide +# if to do the ACL tests or not. +pretest: + echo "$(TEST_ACL)" > ./test/test.ACL ; + echo "$(TEST_SELINUX)" > ./test/test.SELINUX ; + +.PHONY : test +test: $(TARGET) + (cd test; ./test) + +install: + [ -d $(PREFIX)$(BINDIR) ] || mkdir -p $(PREFIX)$(BINDIR) + [ -d $(PREFIX)$(MANDIR) ] || mkdir -p $(PREFIX)$(MANDIR) + [ -d $(PREFIX)$(MANDIR)/man8 ] || mkdir -p $(PREFIX)$(MANDIR)/man8 + [ -d $(PREFIX)$(MANDIR)/man5 ] || mkdir -p $(PREFIX)$(MANDIR)/man5 + + if [ "$(OS_NAME)" = HP-UX ]; then \ + $(INSTALL) $(PROG) $(PREFIX)$(BINDIR) 0755 bin bin; \ + $(INSTALL) $(MAN) $(PREFIX)$(MANDIR)/man`echo $(MAN) | sed "s/.*\.//"` 0644 bin bin; \ + $(INSTALL) $(MAN5) $(PREFIX)$(MANDIR)/man`echo $(MAN5) | sed "s/.*\.//"` 0644 bin bin; \ + else if [ "$(OS_NAME)" = FreeBSD ]; then \ + $(BSD_INSTALL_PROGRAM) $(PROG) $(BINDIR); \ + $(BSD_INSTALL_MAN) $(MAN) $(MANDIR)/man`echo $(MAN) | sed "s/.*\.//"`/$(MAN); \ + $(BSD_INSTALL_MAN) $(MAN5) $(MANDIR)/man`echo $(MAN5) | sed "s/.*\.//"`/$(MAN5); \ + else \ + $(INSTALL) -m 755 $(PROG) $(PREFIX)$(BINDIR); \ + $(INSTALL) -m 644 $(MAN) $(PREFIX)$(MANDIR)/man`echo $(MAN) | sed "s/.*\.//"`/$(MAN); \ + $(INSTALL) -m 644 $(MAN5) $(PREFIX)$(MANDIR)/man`echo $(MAN5) | sed "s/.*\.//"`/$(MAN5); \ + fi; fi + +co: + co RCS/*,v + (cd examples; co RCS/*,v) + +svntag: + svn copy $(SVNURL)/trunk $(SVNURL)/tags/$(SVNTAG) -m "Release $(VERSION)" + +create-archive: + @rm -rf /tmp/logrotate-$(VERSION) /tmp/logrotate + @cd /tmp; svn export $(SVNPUBURL)/tags/$(SVNTAG) logrotate-$(VERSION) + @cd /tmp/logrotate-$(VERSION) + @cd /tmp; tar czSpf logrotate-$(VERSION).tar.gz logrotate-$(VERSION) + @rm -rf /tmp/logrotate-$(VERSION) + @cp /tmp/logrotate-$(VERSION).tar.gz . + @rm -f /tmp/logrotate-$(VERSION).tar.gz + @echo " " + @echo "The final archive is ./logrotate-$(VERSION).tar.gz." + +archive: clean svntag create-archive + +ifeq (.depend,$(wildcard .depend)) +include .depend +endif
diff --git a/logrotate/README.HPUX b/logrotate/README.HPUX new file mode 100644 index 0000000..ad912a7 --- /dev/null +++ b/logrotate/README.HPUX
@@ -0,0 +1,43 @@ +How to build and install logrotate on HP-UX 11.00 (these instructions should +also work on HP-UX 10.20): + +1. Obtain and install the following GNU packages for HP-UX: + binutils 2.9.1 + gcc 2.95.2 + make 3.78.1 + I used the packages at the Software Porting and Archive Centre for HP-UX + at http://hpux.cs.utah.edu/. + + Obtain and install the following GNU/Linux to HP-UX Porting package: + libhplx library + See http://devresource.hp.com/LPK/index.html for downloads. + This library is needed to provide the ??? function. + +2. Obtain, build, and install popt 1.4 (there doesn't seem to be a build at + the Porting Centre.) + See ftp://ftp.rpm.org/pub/rpm/dist/rpm-4.0.x/popt-1.6.4.tar.gz + Install it into the directory of your choice (i.e. + "./configure --prefix=/opt/popt"). + +3. Build logrotate, telling it where to find popt and hplx installation. The + POPT_DIR defaults to /usr/local and HPLX_DIR defaults to /usr/local/hplx: + gmake POPT_DIR=/usr/local HPLX_DIR=/usr/local/hplx + +4. Install logrotate into your desired directory (BASEDIR defaults to + /usr/local): + gmake install BASEDIR=/usr/local + +5. Copy the configuration files into your desired location. + cp examples/logrotate-default /etc/logrotate.conf + mkdir /etc/logrotate.d + +6. Set up a cron job to run logrotate daily. See examples/logrotate.cron. + +7. I also recommend setting CLEAN_ADM=0 in /etc/rc.config.d/clean, and + setting up an init script to use logrotate for this instead. This way, + logrotate manages all logfile pruning. + + +Questions, comments, abuse to: + Paul D. Gear <citecpdg@citec.qld.gov.au>, <paulgear@bigfoot.com> + Danial M. Howard <howadani@isu.edu>, <dmhoward@byu.edu>
diff --git a/logrotate/README.Solaris b/logrotate/README.Solaris new file mode 100644 index 0000000..f2d0a3d --- /dev/null +++ b/logrotate/README.Solaris
@@ -0,0 +1,19 @@ +Steps to build and install logrotate on Solaris 2.6 + +1. Obtain and install the following GNU packages from http://Sunfreeware.com: + gcc 2.95.2 + make 3.80 + popt-1.6.3 + +2. Build and install logrotate: + gmake + gmake install + +OBS.: If you want to use the test script on Solaris 2.6, you'll need to have + bash installed, adjust the path after the sha-bang (#!) properly and + substitute the sintax $(...) for backticks `...` in all + "test-config.?.in" files. + +-- +Fidelis Assis <fidelis@embratel.net.br> +
diff --git a/logrotate/README.md b/logrotate/README.md new file mode 100644 index 0000000..02ea87f --- /dev/null +++ b/logrotate/README.md
@@ -0,0 +1,22 @@ +# logrotate + +The logrotate utility is designed to simplify the administration of log files on a system which generates a lot of log files. Logrotate allows for the automatic rotation compression, removal and mailing of log files. Logrotate can be set to handle a log file daily, weekly, monthly or when the log file gets to a certain size. + +## Download + +The latest release is: + +* [logrotate-3.10.0](https://github.com/logrotate/logrotate/releases/download/3.10.0/logrotate-3.10.0.tar.gz) ([Changelog](https://github.com/logrotate/logrotate/commit/028f1cb4)) + +Previous releases: + +* [logrotate-3.9.2](https://github.com/logrotate/logrotate/releases/download/3.9.2/logrotate-3.9.2.tar.gz) ([Changelog](https://github.com/logrotate/logrotate/releases/tag/3.9.2)) +* [logrotate-3.9.1](https://fedorahosted.org/releases/l/o/logrotate/logrotate-3.9.1.tar.gz) +* [logrotate-3.9.0](https://fedorahosted.org/releases/l/o/logrotate/logrotate-3.9.0.tar.gz) +* [logrotate-3.8.9](https://fedorahosted.org/releases/l/o/logrotate/logrotate-3.8.9.tar.gz) +* [logrotate-3.8.8](https://fedorahosted.org/releases/l/o/logrotate/logrotate-3.8.8.tar.gz) +* [logrotate-3.8.7](https://fedorahosted.org/releases/l/o/logrotate/logrotate-3.8.7.tar.gz) + +# Patches and Questions + +Open issues or pull requests on GitHub.
diff --git a/logrotate/autogen.sh b/logrotate/autogen.sh new file mode 100755 index 0000000..280b871 --- /dev/null +++ b/logrotate/autogen.sh
@@ -0,0 +1,1481 @@ +#!/bin/sh +# a u t o g e n . s h +# +# Copyright (c) 2005-2007 United States Government as represented by +# the U.S. Army Research Laboratory. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided +# with the distribution. +# +# 3. The name of the author may not be used to endorse or promote +# products derived from this software without specific prior written +# permission. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS +# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +# GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +### +# +# Script for automatically preparing the sources for compilation by +# performing the myrid of necessary steps. The script attempts to +# detect proper version support, and outputs warnings about particular +# systems that have autotool peculiarities. +# +# Basically, if everything is set up and installed correctly, the +# script will validate that minimum versions of the GNU Build System +# tools are installed, account for several common configuration +# issues, and then simply run autoreconf for you. +# +# If autoreconf fails, which can happen for many valid configurations, +# this script proceeds to run manual preparation steps effectively +# providing a POSIX shell script (mostly complete) reimplementation of +# autoreconf. +# +# The AUTORECONF, AUTOCONF, AUTOMAKE, LIBTOOLIZE, ACLOCAL, AUTOHEADER +# environment variables and corresponding _OPTIONS variables (e.g. +# AUTORECONF_OPTIONS) may be used to override the default automatic +# detection behaviors. Similarly the _VERSION variables will override +# the minimum required version numbers. +# +# Examples: +# +# To obtain help on usage: +# ./autogen.sh --help +# +# To obtain verbose output: +# ./autogen.sh --verbose +# +# To skip autoreconf and prepare manually: +# AUTORECONF=false ./autogen.sh +# +# To verbosely try running with an older (unsupported) autoconf: +# AUTOCONF_VERSION=2.50 ./autogen.sh --verbose +# +# Author: Christopher Sean Morrison <morrison@brlcad.org> +# +###################################################################### + +# set to minimum acceptible version of autoconf +if [ "x$AUTOCONF_VERSION" = "x" ] ; then + AUTOCONF_VERSION=2.52 +fi +# set to minimum acceptible version of automake +if [ "x$AUTOMAKE_VERSION" = "x" ] ; then + AUTOMAKE_VERSION=1.6.0 +fi +# set to minimum acceptible version of libtool +if [ "x$LIBTOOL_VERSION" = "x" ] ; then + LIBTOOL_VERSION=1.4.2 +fi + + +################## +# ident function # +################## +ident ( ) { + # extract copyright from header + __copyright="`grep Copyright $AUTOGEN_SH | head -${HEAD_N}1 | awk '{print $4}'`" + if [ "x$__copyright" = "x" ] ; then + __copyright="`date +%Y`" + fi + + # extract version from CVS Id string + __id="$Id: autogen.sh,v 14.97 2007/06/18 22:25:02 brlcad Exp $" + __version="`echo $__id | sed 's/.*\([0-9][0-9][0-9][0-9]\)[-\/]\([0-9][0-9]\)[-\/]\([0-9][0-9]\).*/\1\2\3/'`" + if [ "x$__version" = "x" ] ; then + __version="" + fi + + echo "autogen.sh build preparation script by Christopher Sean Morrison" + echo "revised 3-clause BSD-style license, copyright (c) $__copyright" + echo "script version $__version, ISO/IEC 9945 POSIX shell script" +} + + +################## +# USAGE FUNCTION # +################## +usage ( ) { + echo "Usage: $AUTOGEN_SH [-h|--help] [-v|--verbose] [-q|--quiet] [--version]" + echo " --help Help on $NAME_OF_AUTOGEN usage" + echo " --verbose Verbose progress output" + echo " --quiet Quiet suppressed progress output" + echo " --version Only perform GNU Build System version checks" + echo + echo "Description: This script will validate that minimum versions of the" + echo "GNU Build System tools are installed and then run autoreconf for you." + echo "Should autoreconf fail, manual preparation steps will be run" + echo "potentially accounting for several common preparation issues. The" + + echo "AUTORECONF, AUTOCONF, AUTOMAKE, LIBTOOLIZE, ACLOCAL, AUTOHEADER," + echo "PROJECT, & CONFIGURE environment variables and corresponding _OPTIONS" + echo "variables (e.g. AUTORECONF_OPTIONS) may be used to override the" + echo "default automatic detection behavior." + echo + + ident + + return 0 +} + + +########################## +# VERSION_ERROR FUNCTION # +########################## +version_error ( ) { + if [ "x$1" = "x" ] ; then + echo "INTERNAL ERROR: version_error was not provided a version" + exit 1 + fi + if [ "x$2" = "x" ] ; then + echo "INTERNAL ERROR: version_error was not provided an application name" + exit 1 + fi + $ECHO + $ECHO "ERROR: To prepare the ${PROJECT} build system from scratch," + $ECHO " at least version $1 of $2 must be installed." + $ECHO + $ECHO "$NAME_OF_AUTOGEN does not need to be run on the same machine that will" + $ECHO "run configure or make. Either the GNU Autotools will need to be installed" + $ECHO "or upgraded on this system, or $NAME_OF_AUTOGEN must be run on the source" + $ECHO "code on another system and then transferred to here. -- Cheers!" + $ECHO +} + +########################## +# VERSION_CHECK FUNCTION # +########################## +version_check ( ) { + if [ "x$1" = "x" ] ; then + echo "INTERNAL ERROR: version_check was not provided a minimum version" + exit 1 + fi + _min="$1" + if [ "x$2" = "x" ] ; then + echo "INTERNAL ERROR: version check was not provided a comparison version" + exit 1 + fi + _cur="$2" + + # needed to handle versions like 1.10 and 1.4-p6 + _min="`echo ${_min}. | sed 's/[^0-9]/./g' | sed 's/\.\././g'`" + _cur="`echo ${_cur}. | sed 's/[^0-9]/./g' | sed 's/\.\././g'`" + + _min_major="`echo $_min | cut -d. -f1`" + _min_minor="`echo $_min | cut -d. -f2`" + _min_patch="`echo $_min | cut -d. -f3`" + + _cur_major="`echo $_cur | cut -d. -f1`" + _cur_minor="`echo $_cur | cut -d. -f2`" + _cur_patch="`echo $_cur | cut -d. -f3`" + + if [ "x$_min_major" = "x" ] ; then + _min_major=0 + fi + if [ "x$_min_minor" = "x" ] ; then + _min_minor=0 + fi + if [ "x$_min_patch" = "x" ] ; then + _min_patch=0 + fi + if [ "x$_cur_minor" = "x" ] ; then + _cur_major=0 + fi + if [ "x$_cur_minor" = "x" ] ; then + _cur_minor=0 + fi + if [ "x$_cur_patch" = "x" ] ; then + _cur_patch=0 + fi + + $VERBOSE_ECHO "Checking if ${_cur_major}.${_cur_minor}.${_cur_patch} is greater than ${_min_major}.${_min_minor}.${_min_patch}" + + if [ $_min_major -lt $_cur_major ] ; then + return 0 + elif [ $_min_major -eq $_cur_major ] ; then + if [ $_min_minor -lt $_cur_minor ] ; then + return 0 + elif [ $_min_minor -eq $_cur_minor ] ; then + if [ $_min_patch -lt $_cur_patch ] ; then + return 0 + elif [ $_min_patch -eq $_cur_patch ] ; then + return 0 + fi + fi + fi + return 1 +} + + +###################################### +# LOCATE_CONFIGURE_TEMPLATE FUNCTION # +###################################### +locate_configure_template ( ) { + _pwd="`pwd`" + if test -f "./configure.ac" ; then + echo "./configure.ac" + elif test -f "./configure.in" ; then + echo "./configure.in" + elif test -f "$_pwd/configure.ac" ; then + echo "$_pwd/configure.ac" + elif test -f "$_pwd/configure.in" ; then + echo "$_pwd/configure.in" + elif test -f "$PATH_TO_AUTOGEN/configure.ac" ; then + echo "$PATH_TO_AUTOGEN/configure.ac" + elif test -f "$PATH_TO_AUTOGEN/configure.in" ; then + echo "$PATH_TO_AUTOGEN/configure.in" + fi +} + + +################## +# argument check # +################## +ARGS="$*" +PATH_TO_AUTOGEN="`dirname $0`" +NAME_OF_AUTOGEN="`basename $0`" +AUTOGEN_SH="$PATH_TO_AUTOGEN/$NAME_OF_AUTOGEN" + +LIBTOOL_M4="${PATH_TO_AUTOGEN}/misc/libtool.m4" + +if [ "x$HELP" = "x" ] ; then + HELP=no +fi +if [ "x$QUIET" = "x" ] ; then + QUIET=no +fi +if [ "x$VERBOSE" = "x" ] ; then + VERBOSE=no +fi +if [ "x$VERSION_ONLY" = "x" ] ; then + VERSION_ONLY=no +fi +if [ "x$AUTORECONF_OPTIONS" = "x" ] ; then + AUTORECONF_OPTIONS="-i -f" +fi +if [ "x$AUTOCONF_OPTIONS" = "x" ] ; then + AUTOCONF_OPTIONS="-f" +fi +if [ "x$AUTOMAKE_OPTIONS" = "x" ] ; then + AUTOMAKE_OPTIONS="-a -c -f" +fi +ALT_AUTOMAKE_OPTIONS="-a -c" +if [ "x$LIBTOOLIZE_OPTIONS" = "x" ] ; then + LIBTOOLIZE_OPTIONS="--automake -c -f" +fi +ALT_LIBTOOLIZE_OPTIONS="--automake --copy --force" +if [ "x$ACLOCAL_OPTIONS" = "x" ] ; then + ACLOCAL_OPTIONS="" +fi +if [ "x$AUTOHEADER_OPTIONS" = "x" ] ; then + AUTOHEADER_OPTIONS="" +fi +for arg in $ARGS ; do + case "x$arg" in + x--help) HELP=yes ;; + x-[hH]) HELP=yes ;; + x--quiet) QUIET=yes ;; + x-[qQ]) QUIET=yes ;; + x--verbose) VERBOSE=yes ;; + x-[vV]) VERBOSE=yes ;; + x--version) VERSION_ONLY=yes ;; + *) + echo "Unknown option: $arg" + echo + usage + exit 1 + ;; + esac +done + + +##################### +# environment check # +##################### + +# sanity check before recursions potentially begin +if [ ! -f "$AUTOGEN_SH" ] ; then + echo "INTERNAL ERROR: $AUTOGEN_SH does not exist" + if [ ! "x$0" = "x$AUTOGEN_SH" ] ; then + echo "INTERNAL ERROR: dirname/basename inconsistency: $0 != $AUTOGEN_SH" + fi + exit 1 +fi + +# force locale setting to C so things like date output as expected +LC_ALL=C + +# commands that this script expects +for __cmd in echo head tail pwd ; do + echo "test" | $__cmd > /dev/null 2>&1 + if [ $? != 0 ] ; then + echo "INTERNAL ERROR: '${__cmd}' command is required" + exit 2 + fi +done +echo "test" | grep "test" > /dev/null 2>&1 +if test ! x$? = x0 ; then + echo "INTERNAL ERROR: grep command is required" + exit 1 +fi +echo "test" | sed "s/test/test/" > /dev/null 2>&1 +if test ! x$? = x0 ; then + echo "INTERNAL ERROR: sed command is required" + exit 1 +fi + + +# determine the behavior of echo +case `echo "testing\c"; echo 1,2,3`,`echo -n testing; echo 1,2,3` in + *c*,-n*) ECHO_N= ECHO_C=' +' ECHO_T=' ' ;; + *c*,* ) ECHO_N=-n ECHO_C= ECHO_T= ;; + *) ECHO_N= ECHO_C='\c' ECHO_T= ;; +esac + +# determine the behavior of head +case "x`echo 'head' | head -n 1 2>&1`" in + *xhead*) HEAD_N="n " ;; + *) HEAD_N="" ;; +esac + +# determine the behavior of tail +case "x`echo 'tail' | tail -n 1 2>&1`" in + *xtail*) TAIL_N="n " ;; + *) TAIL_N="" ;; +esac + +VERBOSE_ECHO=: +ECHO=: +if [ "x$QUIET" = "xyes" ] ; then + if [ "x$VERBOSE" = "xyes" ] ; then + echo "Verbose output quelled by quiet option. Further output disabled." + fi +else + ECHO=echo + if [ "x$VERBOSE" = "xyes" ] ; then + echo "Verbose output enabled" + VERBOSE_ECHO=echo + fi +fi + + +# allow a recursive run to disable further recursions +if [ "x$RUN_RECURSIVE" = "x" ] ; then + RUN_RECURSIVE=yes +fi + + +################################################ +# check for help arg and bypass version checks # +################################################ +if [ "x`echo $ARGS | sed 's/.*[hH][eE][lL][pP].*/help/'`" = "xhelp" ] ; then + HELP=yes +fi +if [ "x$HELP" = "xyes" ] ; then + usage + $ECHO "---" + $ECHO "Help was requested. No preparation or configuration will be performed." + exit 0 +fi + + +####################### +# set up signal traps # +####################### +untrap_abnormal ( ) { + for sig in 1 2 13 15; do + trap - $sig + done +} + +# do this cleanup whenever we exit. +trap ' + # start from the root + if test -d "$START_PATH" ; then + cd "$START_PATH" + fi + + # restore/delete backup files + if test "x$PFC_INIT" = "x1" ; then + recursive_restore + fi +' 0 + +# trap SIGHUP (1), SIGINT (2), SIGPIPE (13), SIGTERM (15) +for sig in 1 2 13 15; do + trap ' + $ECHO "" + $ECHO "Aborting $NAME_OF_AUTOGEN: caught signal '$sig'" + + # start from the root + if test -d "$START_PATH" ; then + cd "$START_PATH" + fi + + # clean up on abnormal exit + $VERBOSE_ECHO "rm -rf autom4te.cache" + rm -rf autom4te.cache + + if test -f "acinclude.m4.$$.backup" ; then + $VERBOSE_ECHO "cat acinclude.m4.$$.backup > acinclude.m4" + chmod u+w acinclude.m4 + cat acinclude.m4.$$.backup > acinclude.m4 + + $VERBOSE_ECHO "rm -f acinclude.m4.$$.backup" + rm -f acinclude.m4.$$.backup + fi + + { (exit 1); exit 1; } +' $sig +done + + +############################# +# look for a configure file # +############################# +if [ "x$CONFIGURE" = "x" ] ; then + CONFIGURE="`locate_configure_template`" + if [ ! "x$CONFIGURE" = "x" ] ; then + $VERBOSE_ECHO "Found a configure template: $CONFIGURE" + fi +else + $ECHO "Using CONFIGURE environment variable override: $CONFIGURE" +fi +if [ "x$CONFIGURE" = "x" ] ; then + if [ "x$VERSION_ONLY" = "xyes" ] ; then + CONFIGURE=/dev/null + else + $ECHO + $ECHO "A configure.ac or configure.in file could not be located implying" + $ECHO "that the GNU Build System is at least not used in this directory. In" + $ECHO "any case, there is nothing to do here without one of those files." + $ECHO + $ECHO "ERROR: No configure.in or configure.ac file found in `pwd`" + exit 1 + fi +fi + +#################### +# get project name # +#################### +if [ "x$PROJECT" = "x" ] ; then + PROJECT="`grep AC_INIT $CONFIGURE | grep -v '.*#.*AC_INIT' | tail -${TAIL_N}1 | sed 's/^[ ]*AC_INIT(\([^,)]*\).*/\1/' | sed 's/.*\[\(.*\)\].*/\1/'`" + if [ "x$PROJECT" = "xAC_INIT" ] ; then + # projects might be using the older/deprecated arg-less AC_INIT .. look for AM_INIT_AUTOMAKE instead + PROJECT="`grep AM_INIT_AUTOMAKE $CONFIGURE | grep -v '.*#.*AM_INIT_AUTOMAKE' | tail -${TAIL_N}1 | sed 's/^[ ]*AM_INIT_AUTOMAKE(\([^,)]*\).*/\1/' | sed 's/.*\[\(.*\)\].*/\1/'`" + fi + if [ "x$PROJECT" = "xAM_INIT_AUTOMAKE" ] ; then + PROJECT="project" + fi + if [ "x$PROJECT" = "x" ] ; then + PROJECT="project" + fi +else + $ECHO "Using PROJECT environment variable override: $PROJECT" +fi +$ECHO "Preparing the $PROJECT build system...please wait" +$ECHO + + +######################## +# check for autoreconf # +######################## +HAVE_AUTORECONF=no +if [ "x$AUTORECONF" = "x" ] ; then + for AUTORECONF in autoreconf ; do + $VERBOSE_ECHO "Checking autoreconf version: $AUTORECONF --version" + $AUTORECONF --version > /dev/null 2>&1 + if [ $? = 0 ] ; then + HAVE_AUTORECONF=yes + break + fi + done +else + HAVE_AUTORECONF=yes + $ECHO "Using AUTORECONF environment variable override: $AUTORECONF" +fi + + +########################## +# autoconf version check # +########################## +_acfound=no +if [ "x$AUTOCONF" = "x" ] ; then + for AUTOCONF in autoconf ; do + $VERBOSE_ECHO "Checking autoconf version: $AUTOCONF --version" + $AUTOCONF --version > /dev/null 2>&1 + if [ $? = 0 ] ; then + _acfound=yes + break + fi + done +else + _acfound=yes + $ECHO "Using AUTOCONF environment variable override: $AUTOCONF" +fi + +_report_error=no +if [ ! "x$_acfound" = "xyes" ] ; then + $ECHO "ERROR: Unable to locate GNU Autoconf." + _report_error=yes +else + _version="`$AUTOCONF --version | head -${HEAD_N}1 | sed 's/[^0-9]*\([0-9\.][0-9\.]*\)/\1/'`" + if [ "x$_version" = "x" ] ; then + _version="0.0.0" + fi + $ECHO "Found GNU Autoconf version $_version" + version_check "$AUTOCONF_VERSION" "$_version" + if [ $? -ne 0 ] ; then + _report_error=yes + fi +fi +if [ "x$_report_error" = "xyes" ] ; then + version_error "$AUTOCONF_VERSION" "GNU Autoconf" + exit 1 +fi + + +########################## +# automake version check # +########################## +_amfound=no +if [ "x$AUTOMAKE" = "x" ] ; then + for AUTOMAKE in automake ; do + $VERBOSE_ECHO "Checking automake version: $AUTOMAKE --version" + $AUTOMAKE --version > /dev/null 2>&1 + if [ $? = 0 ] ; then + _amfound=yes + break + fi + done +else + _amfound=yes + $ECHO "Using AUTOMAKE environment variable override: $AUTOMAKE" +fi + + +_report_error=no +if [ ! "x$_amfound" = "xyes" ] ; then + $ECHO + $ECHO "ERROR: Unable to locate GNU Automake." + _report_error=yes +else + _version="`$AUTOMAKE --version | head -${HEAD_N}1 | sed 's/[^0-9]*\([0-9\.][0-9\.]*\)/\1/'`" + if [ "x$_version" = "x" ] ; then + _version="0.0.0" + fi + $ECHO "Found GNU Automake version $_version" + version_check "$AUTOMAKE_VERSION" "$_version" + if [ $? -ne 0 ] ; then + _report_error=yes + fi +fi +if [ "x$_report_error" = "xyes" ] ; then + version_error "$AUTOMAKE_VERSION" "GNU Automake" + exit 1 +fi + + +######################## +# check for libtoolize # +######################## +HAVE_LIBTOOLIZE=yes +HAVE_ALT_LIBTOOLIZE=no +_ltfound=no +if [ "x$LIBTOOLIZE" = "x" ] ; then + LIBTOOLIZE=libtoolize + $VERBOSE_ECHO "Checking libtoolize version: $LIBTOOLIZE --version" + $LIBTOOLIZE --version > /dev/null 2>&1 + if [ ! $? = 0 ] ; then + HAVE_LIBTOOLIZE=no + $ECHO + if [ "x$HAVE_AUTORECONF" = "xno" ] ; then + $ECHO "Warning: libtoolize does not appear to be available." + else + $ECHO "Warning: libtoolize does not appear to be available. This means that" + $ECHO "the automatic build preparation via autoreconf will probably not work." + $ECHO "Preparing the build by running each step individually, however, should" + $ECHO "work and will be done automatically for you if autoreconf fails." + fi + + # look for some alternates + for tool in glibtoolize libtoolize15 libtoolize14 libtoolize13 ; do + $VERBOSE_ECHO "Checking libtoolize alternate: $tool --version" + _glibtoolize="`$tool --version > /dev/null 2>&1`" + if [ $? = 0 ] ; then + $VERBOSE_ECHO "Found $tool --version" + _glti="`which $tool`" + if [ "x$_glti" = "x" ] ; then + $VERBOSE_ECHO "Cannot find $tool with which" + continue; + fi + if test ! -f "$_glti" ; then + $VERBOSE_ECHO "Cannot use $tool, $_glti is not a file" + continue; + fi + _gltidir="`dirname $_glti`" + if [ "x$_gltidir" = "x" ] ; then + $VERBOSE_ECHO "Cannot find $tool path with dirname of $_glti" + continue; + fi + if test ! -d "$_gltidir" ; then + $VERBOSE_ECHO "Cannot use $tool, $_gltidir is not a directory" + continue; + fi + HAVE_ALT_LIBTOOLIZE=yes + LIBTOOLIZE="$tool" + $ECHO + $ECHO "Fortunately, $tool was found which means that your system may simply" + $ECHO "have a non-standard or incomplete GNU Autotools install. If you have" + $ECHO "sufficient system access, it may be possible to quell this warning by" + $ECHO "running:" + $ECHO + sudo -V > /dev/null 2>&1 + if [ $? = 0 ] ; then + $ECHO " sudo ln -s $_glti $_gltidir/libtoolize" + $ECHO + else + $ECHO " ln -s $_glti $_gltidir/libtoolize" + $ECHO + $ECHO "Run that as root or with proper permissions to the $_gltidir directory" + $ECHO + fi + _ltfound=yes + break + fi + done + else + _ltfound=yes + fi +else + _ltfound=yes + $ECHO "Using LIBTOOLIZE environment variable override: $LIBTOOLIZE" +fi + + +############################ +# libtoolize version check # +############################ +_report_error=no +if [ ! "x$_ltfound" = "xyes" ] ; then + $ECHO + $ECHO "ERROR: Unable to locate GNU Libtool." + _report_error=yes +else + _version="`$LIBTOOLIZE --version | head -${HEAD_N}1 | sed 's/[^0-9]*\([0-9\.][0-9\.]*\)/\1/'`" + if [ "x$_version" = "x" ] ; then + _version="0.0.0" + fi + $ECHO "Found GNU Libtool version $_version" + version_check "$LIBTOOL_VERSION" "$_version" + if [ $? -ne 0 ] ; then + _report_error=yes + fi +fi +if [ "x$_report_error" = "xyes" ] ; then + version_error "$LIBTOOL_VERSION" "GNU Libtool" + exit 1 +fi + + +##################### +# check for aclocal # +##################### +if [ "x$ACLOCAL" = "x" ] ; then + for ACLOCAL in aclocal ; do + $VERBOSE_ECHO "Checking aclocal version: $ACLOCAL --version" + $ACLOCAL --version > /dev/null 2>&1 + if [ $? = 0 ] ; then + break + fi + done +else + $ECHO "Using ACLOCAL environment variable override: $ACLOCAL" +fi + + +######################## +# check for autoheader # +######################## +if [ "x$AUTOHEADER" = "x" ] ; then + for AUTOHEADER in autoheader ; do + $VERBOSE_ECHO "Checking autoheader version: $AUTOHEADER --version" + $AUTOHEADER --version > /dev/null 2>&1 + if [ $? = 0 ] ; then + break + fi + done +else + $ECHO "Using AUTOHEADER environment variable override: $AUTOHEADER" +fi + + +######################### +# check if version only # +######################### +$VERBOSE_ECHO "Checking whether to only output version information" +if [ "x$VERSION_ONLY" = "xyes" ] ; then + $ECHO + ident + $ECHO "---" + $ECHO "Version requested. No preparation or configuration will be performed." + exit 0 +fi + + +################################# +# PROTECT_FROM_CLOBBER FUNCTION # +################################# +protect_from_clobber ( ) { + PFC_INIT=1 + + # protect COPYING & INSTALL from overwrite by automake. the + # automake force option will (inappropriately) ignore the existing + # contents of a COPYING and/or INSTALL files (depending on the + # version) instead of just forcing *missing* files like it does + # for AUTHORS, NEWS, and README. this is broken but extremely + # prevalent behavior, so we protect against it by keeping a backup + # of the file that can later be restored. + + if test -f COPYING ; then + if test -f COPYING.$$.protect_from_automake.backup ; then + $VERBOSE_ECHO "Already backed up COPYING in `pwd`" + else + $VERBOSE_ECHO "Backing up COPYING in `pwd`" + $VERBOSE_ECHO "cp -p COPYING COPYING.$$.protect_from_automake.backup" + cp -p COPYING COPYING.$$.protect_from_automake.backup + fi + fi + if test -f INSTALL ; then + if test -f INSTALL.$$.protect_from_automake.backup ; then + $VERBOSE_ECHO "Already backed up INSTALL in `pwd`" + else + $VERBOSE_ECHO "Backing up INSTALL in `pwd`" + $VERBOSE_ECHO "cp -p INSTALL INSTALL.$$.protect_from_automake.backup" + cp -p INSTALL INSTALL.$$.protect_from_automake.backup + fi + fi +} + + +############################## +# RECURSIVE_PROTECT FUNCTION # +############################## +recursive_protect ( ) { + + # for projects using recursive configure, run the build + # preparation steps for the subdirectories. this function assumes + # START_PATH was set to pwd before recursion begins so that + # relative paths work. + + # git 'r done, protect COPYING and INSTALL from being clobbered + protect_from_clobber + + if test -d autom4te.cache ; then + $VERBOSE_ECHO "Found an autom4te.cache directory, deleting it" + $VERBOSE_ECHO "rm -rf autom4te.cache" + rm -rf autom4te.cache + fi + + # find configure template + _configure="`locate_configure_template`" + if [ "x$_configure" = "x" ] ; then + return + fi + # $VERBOSE_ECHO "Looking for configure template found `pwd`/$_configure" + + # look for subdirs + # $VERBOSE_ECHO "Looking for subdirs in `pwd`" + _det_config_subdirs="`grep AC_CONFIG_SUBDIRS $_configure | grep -v '.*#.*AC_CONFIG_SUBDIRS' | sed 's/^[ ]*AC_CONFIG_SUBDIRS(\(.*\)).*/\1/' | sed 's/.*\[\(.*\)\].*/\1/'`" + CHECK_DIRS="" + for dir in $_det_config_subdirs ; do + if test -d "`pwd`/$dir" ; then + CHECK_DIRS="$CHECK_DIRS \"`pwd`/$dir\"" + fi + done + + # process subdirs + if [ ! "x$CHECK_DIRS" = "x" ] ; then + $VERBOSE_ECHO "Recursively scanning the following directories:" + $VERBOSE_ECHO " $CHECK_DIRS" + for dir in $CHECK_DIRS ; do + $VERBOSE_ECHO "Protecting files from automake in $dir" + cd "$START_PATH" + eval "cd $dir" + + # recursively git 'r done + recursive_protect + done + fi +} # end of recursive_protect + + +############################# +# RESTORE_CLOBBERED FUNCION # +############################# +restore_clobbered ( ) { + + # The automake (and autoreconf by extension) -f/--force-missing + # option may overwrite COPYING and INSTALL even if they do exist. + # Here we restore the files if necessary. + + spacer=no + + # COPYING + if test -f COPYING.$$.protect_from_automake.backup ; then + if test -f COPYING ; then + # compare entire content, restore if needed + if test "x`cat COPYING`" != "x`cat COPYING.$$.protect_from_automake.backup`" ; then + if test "x$spacer" = "xno" ; then + $VERBOSE_ECHO + spacer=yes + fi + # restore the backup + $VERBOSE_ECHO "Restoring COPYING from backup (automake -f likely clobbered it)" + $VERBOSE_ECHO "rm -f COPYING" + rm -f COPYING + $VERBOSE_ECHO "mv COPYING.$$.protect_from_automake.backup COPYING" + mv COPYING.$$.protect_from_automake.backup COPYING + fi # check contents + elif test -f COPYING.$$.protect_from_automake.backup ; then + $VERBOSE_ECHO "mv COPYING.$$.protect_from_automake.backup COPYING" + mv COPYING.$$.protect_from_automake.backup COPYING + fi # -f COPYING + + # just in case + $VERBOSE_ECHO "rm -f COPYING.$$.protect_from_automake.backup" + rm -f COPYING.$$.protect_from_automake.backup + fi # -f COPYING.$$.protect_from_automake.backup + + # INSTALL + if test -f INSTALL.$$.protect_from_automake.backup ; then + if test -f INSTALL ; then + # compare entire content, restore if needed + if test "x`cat INSTALL`" != "x`cat INSTALL.$$.protect_from_automake.backup`" ; then + if test "x$spacer" = "xno" ; then + $VERBOSE_ECHO + spacer=yes + fi + # restore the backup + $VERBOSE_ECHO "Restoring INSTALL from backup (automake -f likely clobbered it)" + $VERBOSE_ECHO "rm -f INSTALL" + rm -f INSTALL + $VERBOSE_ECHO "mv INSTALL.$$.protect_from_automake.backup INSTALL" + mv INSTALL.$$.protect_from_automake.backup INSTALL + fi # check contents + elif test -f INSTALL.$$.protect_from_automake.backup ; then + $VERBOSE_ECHO "mv INSTALL.$$.protect_from_automake.backup INSTALL" + mv INSTALL.$$.protect_from_automake.backup INSTALL + fi # -f INSTALL + + # just in case + $VERBOSE_ECHO "rm -f INSTALL.$$.protect_from_automake.backup" + rm -f INSTALL.$$.protect_from_automake.backup + fi # -f INSTALL.$$.protect_from_automake.backup + + CONFIGURE="`locate_configure_template`" + if [ "x$CONFIGURE" = "x" ] ; then + return + fi + + _aux_dir="`grep AC_CONFIG_AUX_DIR $CONFIGURE | grep -v '.*#.*AC_CONFIG_AUX_DIR' | tail -${TAIL_N}1 | sed 's/^[ ]*AC_CONFIG_AUX_DIR(\(.*\)).*/\1/' | sed 's/.*\[\(.*\)\].*/\1/'`" + if test ! -d "$_aux_dir" ; then + _aux_dir=. + fi + + for file in config.guess config.sub ltmain.sh ; do + if test -f "${_aux_dir}/${file}" ; then + $VERBOSE_ECHO "rm -f \"${_aux_dir}/${file}.backup\"" + rm -f "${_aux_dir}/${file}.backup" + fi + done +} # end of restore_clobbered + + +############################## +# RECURSIVE_RESTORE FUNCTION # +############################## +recursive_restore ( ) { + + # restore COPYING and INSTALL from backup if they were clobbered + # for each directory recursively. + + # git 'r undone + restore_clobbered + + # find configure template + _configure="`locate_configure_template`" + if [ "x$_configure" = "x" ] ; then + return + fi + + # look for subdirs + _det_config_subdirs="`grep AC_CONFIG_SUBDIRS $_configure | grep -v '.*#.*AC_CONFIG_SUBDIRS' | sed 's/^[ ]*AC_CONFIG_SUBDIRS(\(.*\)).*/\1/' | sed 's/.*\[\(.*\)\].*/\1/'`" + CHECK_DIRS="" + for dir in $_det_config_subdirs ; do + if test -d "`pwd`/$dir" ; then + CHECK_DIRS="$CHECK_DIRS \"`pwd`/$dir\"" + fi + done + + # process subdirs + if [ ! "x$CHECK_DIRS" = "x" ] ; then + $VERBOSE_ECHO "Recursively scanning the following directories:" + $VERBOSE_ECHO " $CHECK_DIRS" + for dir in $CHECK_DIRS ; do + $VERBOSE_ECHO "Checking files for automake damage in $dir" + cd "$START_PATH" + eval "cd $dir" + + # recursively git 'r undone + recursive_restore + done + fi +} # end of recursive_restore + + +####################### +# INITIALIZE FUNCTION # +####################### +initialize ( ) { + + # this routine performs a variety of directory-specific + # initializations. some are sanity checks, some are preventive, + # and some are necessary setup detection. + # + # this function sets: + # CONFIGURE + # SEARCH_DIRS + # CONFIG_SUBDIRS + + ################################## + # check for a configure template # + ################################## + CONFIGURE="`locate_configure_template`" + if [ "x$CONFIGURE" = "x" ] ; then + $ECHO + $ECHO "A configure.ac or configure.in file could not be located implying" + $ECHO "that the GNU Build System is at least not used in this directory. In" + $ECHO "any case, there is nothing to do here without one of those files." + $ECHO + $ECHO "ERROR: No configure.in or configure.ac file found in `pwd`" + exit 1 + fi + + ##################### + # detect an aux dir # + ##################### + _aux_dir="`grep AC_CONFIG_AUX_DIR $CONFIGURE | grep -v '.*#.*AC_CONFIG_AUX_DIR' | tail -${TAIL_N}1 | sed 's/^[ ]*AC_CONFIG_AUX_DIR(\(.*\)).*/\1/' | sed 's/.*\[\(.*\)\].*/\1/'`" + if test ! -d "$_aux_dir" ; then + _aux_dir=. + else + $VERBOSE_ECHO "Detected auxillary directory: $_aux_dir" + fi + + ################################ + # detect a recursive configure # + ################################ + CONFIG_SUBDIRS="" + _det_config_subdirs="`grep AC_CONFIG_SUBDIRS $CONFIGURE | grep -v '.*#.*AC_CONFIG_SUBDIRS' | sed 's/^[ ]*AC_CONFIG_SUBDIRS(\(.*\)).*/\1/' | sed 's/.*\[\(.*\)\].*/\1/'`" + for dir in $_det_config_subdirs ; do + if test -d "`pwd`/$dir" ; then + $VERBOSE_ECHO "Detected recursive configure directory: `pwd`/$dir" + CONFIG_SUBDIRS="$CONFIG_SUBDIRS `pwd`/$dir" + fi + done + + ################################################## + # make sure certain generated files do not exist # + ################################################## + for file in config.guess config.sub ltmain.sh ; do + if test -f "${_aux_dir}/${file}" ; then + $VERBOSE_ECHO "mv -f \"${_aux_dir}/${file}\" \"${_aux_dir}/${file}.backup\"" + mv -f "${_aux_dir}/${file}" "${_aux_dir}/${file}.backup" + fi + done + + ############################ + # search alternate m4 dirs # + ############################ + SEARCH_DIRS="" + for dir in m4 ; do + if [ -d $dir ] ; then + $VERBOSE_ECHO "Found extra aclocal search directory: $dir" + SEARCH_DIRS="$SEARCH_DIRS -I $dir" + fi + done + + ###################################### + # remove any previous build products # + ###################################### + if test -d autom4te.cache ; then + $VERBOSE_ECHO "Found an autom4te.cache directory, deleting it" + $VERBOSE_ECHO "rm -rf autom4te.cache" + rm -rf autom4te.cache + fi +# tcl/tk (and probably others) have a customized aclocal.m4, so can't delete it +# if test -f aclocal.m4 ; then +# $VERBOSE_ECHO "Found an aclocal.m4 file, deleting it" +# $VERBOSE_ECHO "rm -f aclocal.m4" +# rm -f aclocal.m4 +# fi + +} # end of initialize() + + +############## +# initialize # +############## + +# stash path +START_PATH="`pwd`" + +# Before running autoreconf or manual steps, some prep detection work +# is necessary or useful. Only needs to occur once per directory, but +# does need to traverse the entire subconfigure hierarchy to protect +# files from being clobbered even by autoreconf. +recursive_protect + +# start from where we started +cd "$START_PATH" + +# get ready to process +initialize + + +############################################ +# prepare build via autoreconf or manually # +############################################ +reconfigure_manually=no +if [ "x$HAVE_AUTORECONF" = "xyes" ] ; then + $ECHO + $ECHO $ECHO_N "Automatically preparing build ... $ECHO_C" + + $VERBOSE_ECHO "$AUTORECONF $SEARCH_DIRS $AUTORECONF_OPTIONS" + autoreconf_output="`$AUTORECONF $SEARCH_DIRS $AUTORECONF_OPTIONS 2>&1`" + ret=$? + $VERBOSE_ECHO "$autoreconf_output" + + if [ ! $ret = 0 ] ; then + if [ "x$HAVE_ALT_LIBTOOLIZE" = "xyes" ] ; then + if [ ! "x`echo \"$autoreconf_output\" | grep libtoolize | grep \"No such file or directory\"`" = "x" ] ; then + $ECHO + $ECHO "Warning: autoreconf failed but due to what is usually a common libtool" + $ECHO "misconfiguration issue. This problem is encountered on systems that" + $ECHO "have installed libtoolize under a different name without providing a" + $ECHO "symbolic link or without setting the LIBTOOLIZE environment variable." + $ECHO + $ECHO "Restarting the preparation steps with LIBTOOLIZE set to $LIBTOOLIZE" + + export LIBTOOLIZE + RUN_RECURSIVE=no + export RUN_RECURSIVE + untrap_abnormal + + $VERBOSE_ECHO sh $AUTOGEN_SH "$1" "$2" "$3" "$4" "$5" "$6" "$7" "$8" "$9" + sh "$AUTOGEN_SH" "$1" "$2" "$3" "$4" "$5" "$6" "$7" "$8" "$9" + exit $? + fi + fi + + $ECHO "Warning: $AUTORECONF failed" + + if test -f ltmain.sh ; then + $ECHO "libtoolize being run by autoreconf is not creating ltmain.sh in the auxillary directory like it should" + fi + + $ECHO "Attempting to run the preparation steps individually" + reconfigure_manually=yes + fi +else + reconfigure_manually=yes +fi + + +############################ +# LIBTOOL_FAILURE FUNCTION # +############################ +libtool_failure ( ) { + + # libtool is rather error-prone in comparison to the other + # autotools and this routine attempts to compensate for some + # common failures. the output after a libtoolize failure is + # parsed for an error related to AC_PROG_LIBTOOL and if found, we + # attempt to inject a project-provided libtool.m4 file. + + _autoconf_output="$1" + + if [ "x$RUN_RECURSIVE" = "xno" ] ; then + # we already tried the libtool.m4, don't try again + return 1 + fi + + if test -f "$LIBTOOL_M4" ; then + found_libtool="`$ECHO $_autoconf_output | grep AC_PROG_LIBTOOL`" + if test ! "x$found_libtool" = "x" ; then + if test -f acinclude.m4 ; then + rm -f acinclude.m4.$$.backup + $VERBOSE_ECHO "cat acinclude.m4 > acinclude.m4.$$.backup" + cat acinclude.m4 > acinclude.m4.$$.backup + fi + $VERBOSE_ECHO "cat \"$LIBTOOL_M4\" >> acinclude.m4" + chmod u+w acinclude.m4 + cat "$LIBTOOL_M4" >> acinclude.m4 + + # don't keep doing this + RUN_RECURSIVE=no + export RUN_RECURSIVE + untrap_abnormal + + $ECHO + $ECHO "Restarting the preparation steps with libtool macros in acinclude.m4" + $VERBOSE_ECHO sh $AUTOGEN_SH "$1" "$2" "$3" "$4" "$5" "$6" "$7" "$8" "$9" + sh "$AUTOGEN_SH" "$1" "$2" "$3" "$4" "$5" "$6" "$7" "$8" "$9" + exit $? + fi + fi +} + + +########################### +# MANUAL_AUTOGEN FUNCTION # +########################### +manual_autogen ( ) { + + ################################################## + # Manual preparation steps taken are as follows: # + # aclocal [-I m4] # + # libtoolize --automake -c -f # + # aclocal [-I m4] # + # autoconf -f # + # autoheader # + # automake -a -c -f # + ################################################## + + ########### + # aclocal # + ########### + $VERBOSE_ECHO "$ACLOCAL $SEARCH_DIRS $ACLOCAL_OPTIONS" + aclocal_output="`$ACLOCAL $SEARCH_DIRS $ACLOCAL_OPTIONS 2>&1`" + ret=$? + $VERBOSE_ECHO "$aclocal_output" + if [ ! $ret = 0 ] ; then $ECHO "ERROR: $ACLOCAL failed" && exit 2 ; fi + + ############## + # libtoolize # + ############## + need_libtoolize=no + for feature in AC_PROG_LIBTOOL LT_INIT ; do + $VERBOSE_ECHO "Searching for $feature in $CONFIGURE" + found="`grep \"^$feature.*\" $CONFIGURE`" + if [ ! "x$found" = "x" ] ; then + need_libtoolize=yes + break + fi + done + if [ "x$need_libtoolize" = "xyes" ] ; then + if [ "x$HAVE_LIBTOOLIZE" = "xyes" ] ; then + $VERBOSE_ECHO "$LIBTOOLIZE $LIBTOOLIZE_OPTIONS" + libtoolize_output="`$LIBTOOLIZE $LIBTOOLIZE_OPTIONS 2>&1`" + ret=$? + $VERBOSE_ECHO "$libtoolize_output" + + if [ ! $ret = 0 ] ; then $ECHO "ERROR: $LIBTOOLIZE failed" && exit 2 ; fi + else + if [ "x$HAVE_ALT_LIBTOOLIZE" = "xyes" ] ; then + $VERBOSE_ECHO "$LIBTOOLIZE $ALT_LIBTOOLIZE_OPTIONS" + libtoolize_output="`$LIBTOOLIZE $ALT_LIBTOOLIZE_OPTIONS 2>&1`" + ret=$? + $VERBOSE_ECHO "$libtoolize_output" + + if [ ! $ret = 0 ] ; then $ECHO "ERROR: $LIBTOOLIZE failed" && exit 2 ; fi + fi + fi + + ########### + # aclocal # + ########### + # re-run again as instructed by libtoolize + $VERBOSE_ECHO "$ACLOCAL $SEARCH_DIRS $ACLOCAL_OPTIONS" + aclocal_output="`$ACLOCAL $SEARCH_DIRS $ACLOCAL_OPTIONS 2>&1`" + ret=$? + $VERBOSE_ECHO "$aclocal_output" + + # libtoolize might put ltmain.sh in the wrong place + if test -f ltmain.sh ; then + if test ! -f "${_aux_dir}/ltmain.sh" ; then + $ECHO + $ECHO "Warning: $LIBTOOLIZE is creating ltmain.sh in the wrong directory" + $ECHO + $ECHO "Fortunately, the problem can be worked around by simply copying the" + $ECHO "file to the appropriate location (${_aux_dir}/). This has been done for you." + $ECHO + $VERBOSE_ECHO "cp -p ltmain.sh \"${_aux_dir}/ltmain.sh\"" + cp -p ltmain.sh "${_aux_dir}/ltmain.sh" + $ECHO $ECHO_N "Continuing build preparation ... $ECHO_C" + fi + fi # ltmain.sh + fi # need_libtoolize + + ############ + # autoconf # + ############ + $VERBOSE_ECHO + $VERBOSE_ECHO "$AUTOCONF $AUTOCONF_OPTIONS" + autoconf_output="`$AUTOCONF $AUTOCONF_OPTIONS 2>&1`" + ret=$? + $VERBOSE_ECHO "$autoconf_output" + + if [ ! $ret = 0 ] ; then + # retry without the -f and check for usage of macros that are too new + ac2_59_macros="AC_C_RESTRICT AC_INCLUDES_DEFAULT AC_LANG_ASSERT AC_LANG_WERROR AS_SET_CATFILE" + ac2_55_macros="AC_COMPILER_IFELSE AC_FUNC_MBRTOWC AC_HEADER_STDBOOL AC_LANG_CONFTEST AC_LANG_SOURCE AC_LANG_PROGRAM AC_LANG_CALL AC_LANG_FUNC_TRY_LINK AC_MSG_FAILURE AC_PREPROC_IFELSE" + ac2_54_macros="AC_C_BACKSLASH_A AC_CONFIG_LIBOBJ_DIR AC_GNU_SOURCE AC_PROG_EGREP AC_PROG_FGREP AC_REPLACE_FNMATCH AC_FUNC_FNMATCH_GNU AC_FUNC_REALLOC AC_TYPE_MBSTATE_T" + + macros_to_search="" + ac_major="`echo ${AUTOCONF_VERSION}. | cut -d. -f1 | sed 's/[^0-9]//g'`" + ac_minor="`echo ${AUTOCONF_VERSION}. | cut -d. -f2 | sed 's/[^0-9]//g'`" + + if [ $ac_major -lt 2 ] ; then + macros_to_search="$ac2_59_macros $ac2_55_macros $ac2_54_macros" + else + if [ $ac_minor -lt 54 ] ; then + macros_to_search="$ac2_59_macros $ac2_55_macros $ac2_54_macros" + elif [ $ac_minor -lt 55 ] ; then + macros_to_search="$ac2_59_macros $ac2_55_macros" + elif [ $ac_minor -lt 59 ] ; then + macros_to_search="$ac2_59_macros" + fi + fi + + configure_ac_macros=__none__ + for feature in $macros_to_search ; do + $VERBOSE_ECHO "Searching for $feature in $CONFIGURE" + found="`grep \"^$feature.*\" $CONFIGURE`" + if [ ! "x$found" = "x" ] ; then + if [ "x$configure_ac_macros" = "x__none__" ] ; then + configure_ac_macros="$feature" + else + configure_ac_macros="$feature $configure_ac_macros" + fi + fi + done + if [ ! "x$configure_ac_macros" = "x__none__" ] ; then + $ECHO + $ECHO "Warning: Unsupported macros were found in $CONFIGURE" + $ECHO + $ECHO "The `echo $CONFIGURE | basename` file was scanned in order to determine if any" + $ECHO "unsupported macros are used that exceed the minimum version" + $ECHO "settings specified within this file. As such, the following macros" + $ECHO "should be removed from configure.ac or the version numbers in this" + $ECHO "file should be increased:" + $ECHO + $ECHO "$configure_ac_macros" + $ECHO + $ECHO $ECHO_N "Ignorantly continuing build preparation ... $ECHO_C" + fi + + ################### + # autoconf, retry # + ################### + $VERBOSE_ECHO + $VERBOSE_ECHO "$AUTOCONF" + autoconf_output="`$AUTOCONF 2>&1`" + ret=$? + $VERBOSE_ECHO "$autoconf_output" + + if [ ! $ret = 0 ] ; then + # test if libtool is busted + libtool_failure "$autoconf_output" + + # let the user know what went wrong + cat <<EOF +$autoconf_output +EOF + $ECHO "ERROR: $AUTOCONF failed" + exit 2 + else + # autoconf sans -f and possibly sans unsupported options succeed so warn verbosely + $ECHO + $ECHO "Warning: autoconf seems to have succeeded by removing the following options:" + $ECHO " AUTOCONF_OPTIONS=\"$AUTOCONF_OPTIONS\"" + $ECHO + $ECHO "Removing those options should not be necessary and indicate some other" + $ECHO "problem with the build system. The build preparation is highly suspect" + $ECHO "and may result in configuration or compilation errors. Consider" + if [ "x$VERBOSE_ECHO" = "x:" ] ; then + $ECHO "rerunning the build preparation with verbose output enabled." + $ECHO " $AUTOGEN_SH --verbose" + else + $ECHO "reviewing the minimum GNU Autotools version settings contained in" + $ECHO "this script along with the macros being used in your `echo $CONFIGURE | basename` file." + fi + $ECHO + $ECHO $ECHO_N "Continuing build preparation ... $ECHO_C" + fi # autoconf ret = 0 + fi # autoconf ret = 0 + + ############## + # autoheader # + ############## + need_autoheader=no + for feature in AM_CONFIG_HEADER AC_CONFIG_HEADER ; do + $VERBOSE_ECHO "Searching for $feature in $CONFIGURE" + found="`grep \"^$feature.*\" $CONFIGURE`" + if [ ! "x$found" = "x" ] ; then + need_autoheader=yes + break + fi + done + if [ "x$need_autoheader" = "xyes" ] ; then + $VERBOSE_ECHO "$AUTOHEADER $AUTOHEADER_OPTIONS" + autoheader_output="`$AUTOHEADER $AUTOHEADER_OPTIONS 2>&1`" + ret=$? + $VERBOSE_ECHO "$autoheader_output" + if [ ! $ret = 0 ] ; then $ECHO "ERROR: $AUTOHEADER failed" && exit 2 ; fi + fi # need_autoheader + + ############ + # automake # + ############ + need_automake=no + for feature in AM_INIT_AUTOMAKE ; do + $VERBOSE_ECHO "Searching for $feature in $CONFIGURE" + found="`grep \"^$feature.*\" $CONFIGURE`" + if [ ! "x$found" = "x" ] ; then + need_automake=yes + break + fi + done + + if [ "x$need_automake" = "xyes" ] ; then + $VERBOSE_ECHO "$AUTOMAKE $AUTOMAKE_OPTIONS" + automake_output="`$AUTOMAKE $AUTOMAKE_OPTIONS 2>&1`" + ret=$? + $VERBOSE_ECHO "$automake_output" + + if [ ! $ret = 0 ] ; then + + ################### + # automake, retry # + ################### + $VERBOSE_ECHO + $VERBOSE_ECHO "$AUTOMAKE $ALT_AUTOMAKE_OPTIONS" + # retry without the -f + automake_output="`$AUTOMAKE $ALT_AUTOMAKE_OPTIONS 2>&1`" + ret=$? + $VERBOSE_ECHO "$automake_output" + + if [ ! $ret = 0 ] ; then + # test if libtool is busted + libtool_failure "$automake_output" + + # let the user know what went wrong + cat <<EOF +$automake_output +EOF + $ECHO "ERROR: $AUTOMAKE failed" + exit 2 + fi # automake retry + fi # automake ret = 0 + fi # need_automake +} # end of manual_autogen + + +##################################### +# RECURSIVE_MANUAL_AUTOGEN FUNCTION # +##################################### +recursive_manual_autogen ( ) { + + # run the build preparation steps manually for this directory + manual_autogen + + # for projects using recursive configure, run the build + # preparation steps for the subdirectories. + if [ ! "x$CONFIG_SUBDIRS" = "x" ] ; then + $VERBOSE_ECHO "Recursively configuring the following directories:" + $VERBOSE_ECHO " $CONFIG_SUBDIRS" + for dir in $CONFIG_SUBDIRS ; do + $VERBOSE_ECHO "Processing recursive configure in $dir" + cd "$START_PATH" + cd "$dir" + + # new directory, prepare + initialize + + # run manual steps for the subdir and any others below + recursive_manual_autogen + done + fi +} + + +################################ +# run manual preparation steps # +################################ +if [ "x$reconfigure_manually" = "xyes" ] ; then + $ECHO + $ECHO $ECHO_N "Preparing build ... $ECHO_C" + + recursive_manual_autogen +fi + + +######################### +# restore and summarize # +######################### +cd "$START_PATH" + +# restore COPYING and INSTALL from backup if necessary +recursive_restore + +# make sure we end up with a configure script +config_ac="`locate_configure_template`" +config="`echo $config_ac | sed 's/\.ac$//' | sed 's/\.in$//'`" +if [ "x$config" = "x" ] ; then + $VERBOSE_ECHO "Could not locate the configure template (from `pwd`)" +fi + +# summarize +$ECHO "done" +$ECHO +if test "x$config" = "x" -o ! -f "$config" ; then + $ECHO "WARNING: The $PROJECT build system should now be prepared but there" + $ECHO "does not seem to be a resulting configure file. This is unexpected" + $ECHO "and likely the result of an error. You should run $NAME_OF_AUTOGEN" + $ECHO "with the --verbose option to get more details on a potential" + $ECHO "misconfiguration." +else + $ECHO "The $PROJECT build system is now prepared. To build here, run:" + $ECHO " $config" + $ECHO " make" +fi + + +# Local Variables: +# mode: sh +# tab-width: 8 +# sh-basic-offset: 4 +# sh-indentation: 4 +# indent-tabs-mode: t +# End: +# ex: shiftwidth=4 tabstop=8
diff --git a/logrotate/basenames.c b/logrotate/basenames.c new file mode 100644 index 0000000..5073b85 --- /dev/null +++ b/logrotate/basenames.c
@@ -0,0 +1,48 @@ +#include <stdlib.h> +#include <string.h> + +#include "basenames.h" + +/* Return NAME with any leading path stripped off. */ + +char *ourBaseName(char *name) +{ + char *base; + + base = strrchr(name, '/'); + return base ? base + 1 : name; +} + +static void stripTrailingSlashes(char *path) +{ + char *last; + + last = path + strlen(path) - 1; + while (last > path && *last == '/') + *last-- = '\0'; +} + +char *ourDirName(char *origname) +{ + char *slash; + char *name; + + name = strdup(origname); + + stripTrailingSlashes(name); + + slash = strrchr(name, '/'); + + if (!slash) { + /* No slash, must be current directory */ + free(name); + /* strdup used, as the return value will be free()ed at some point */ + return strdup("."); + } else { + /* Remove any trailing slashes and final element. */ + while (slash > name && *slash == '/') + --slash; + slash[1] = '\0'; + return name; + } +}
diff --git a/logrotate/basenames.h b/logrotate/basenames.h new file mode 100644 index 0000000..078b923 --- /dev/null +++ b/logrotate/basenames.h
@@ -0,0 +1,9 @@ +#ifndef H_BASENAMES +#define H_BASENAMES + +/* returns a pointer inside of name */ +char *ourBaseName(char *name); +/* returns a malloc'd string which must be freed by the caller */ +char *ourDirName(char *origname); + +#endif
diff --git a/logrotate/config.c b/logrotate/config.c new file mode 100644 index 0000000..188cb97 --- /dev/null +++ b/logrotate/config.c
@@ -0,0 +1,1702 @@ +#include "queue.h" +/* Alloca is defined in stdlib.h in NetBSD */ +#ifndef __NetBSD__ +#include <alloca.h> +#endif +#include <limits.h> +#include <ctype.h> +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <glob.h> +#include <grp.h> +#include <popt.h> +#include <pwd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <time.h> +#include <unistd.h> +#include <assert.h> +#include <wchar.h> +#include <wctype.h> +#include <fnmatch.h> +#include <sys/mman.h> + +#include "basenames.h" +#include "log.h" +#include "logrotate.h" + +#if !defined(GLOB_ABORTED) && defined(GLOB_ABEND) +#define GLOB_ABORTED GLOB_ABEND +#endif + +#define REALLOC_STEP 10 + +#if defined(SunOS) +#include <limits.h> +#if !defined(isblank) +#define isblank(c) ( (c) == ' ' || (c) == '\t' ) ? 1 : 0 +#endif +#endif + +#ifdef __hpux +#include "asprintf.c" +#endif + +#if !defined(HAVE_ASPRINTF) && !defined(_FORTIFY_SOURCE) +#include <stdarg.h> + +int asprintf(char **string_ptr, const char *format, ...) +{ + va_list arg; + char *str; + int size; + int rv; + + va_start(arg, format); + size = vsnprintf(NULL, 0, format, arg); + size++; + va_start(arg, format); + str = malloc(size); + if (str == NULL) { + va_end(arg); + /* + * Strictly speaking, GNU asprintf doesn't do this, + * but the caller isn't checking the return value. + */ + fprintf(stderr, "failed to allocate memory\\n"); + exit(1); + } + rv = vsnprintf(str, size, format, arg); + va_end(arg); + + *string_ptr = str; + return (rv); +} + +#endif + +#if !defined(HAVE_STRNDUP) +char *strndup(const char *s, size_t n) +{ + size_t nAvail; + char *p; + + if(!s) + return NULL; + + /* min() */ + nAvail = strlen(s) + 1; + if ( (n + 1) < nAvail) + nAvail = n + 1; + + p = malloc(nAvail); + if (!p) + return NULL; + memcpy(p, s, nAvail); + p[nAvail - 1] = 0; + return p; +} +#endif + +enum { + STATE_DEFAULT = 2, + STATE_SKIP_LINE = 4, + STATE_DEFINITION_END = 8, + STATE_SKIP_CONFIG = 16, + STATE_LOAD_SCRIPT = 32, + STATE_ERROR = 64, +}; + +static char *defTabooExts[] = { ".rpmsave", ".rpmorig", "~", ",v", + ".disabled", ".dpkg-old", ".dpkg-dist", ".dpkg-new", ".cfsaved", + ".ucf-old", ".ucf-dist", ".ucf-new", + ".rpmnew", ".swp", ".cfsaved", ".rhn-cfg-tmp-*" +}; +static int defTabooCount = sizeof(defTabooExts) / sizeof(char *); + +/* I shouldn't use globals here :-( */ +static char **tabooExts = NULL; +int tabooCount = 0; +static int glob_errno = 0; + +static int readConfigFile(const char *configFile, struct logInfo *defConfig); +static int globerr(const char *pathname, int theerr); + +static char *isolateLine(char **strt, char **buf, size_t length) { + char *endtag, *start, *tmp; + start = *strt; + endtag = start; + while (endtag - *buf < length && *endtag != '\n') { + endtag++;} + if (endtag - *buf > length) + return NULL; + tmp = endtag - 1; + while (isspace((unsigned char)*endtag)) + endtag--; + char *key = strndup(start, endtag - start + 1); + *strt = tmp; + return key; +} + +static char *isolateValue(const char *fileName, int lineNum, char *key, + char **startPtr, char **buf, size_t length) +{ + char *chptr = *startPtr; + + while (chptr - *buf < length && isblank((unsigned char)*chptr)) + chptr++; + if (chptr - *buf < length && *chptr == '=') { + chptr++; + while ( chptr - *buf < length && isblank((unsigned char)*chptr)) + chptr++; + } + + if (chptr - *buf < length && *chptr == '\n') { + message(MESS_ERROR, "%s:%d argument expected after %s\n", + fileName, lineNum, key); + return NULL; + } + + *startPtr = chptr; + return isolateLine(startPtr, buf, length); +} + +static char *isolateWord(char **strt, char **buf, size_t length) { + char *endtag, *start; + start = *strt; + while (start - *buf < length && isblank((unsigned char)*start)) + start++; + endtag = start; + while (endtag - *buf < length && isalpha((unsigned char)*endtag)) { + endtag++;} + if (endtag - *buf > length) + return NULL; + char *key = strndup(start, endtag - start); + *strt = endtag; + return key; +} + +static char *readPath(const char *configFile, int lineNum, char *key, + char **startPtr, char **buf, size_t length) +{ + char *chptr; + char *start = *startPtr; + char *path; + + wchar_t pwc; + size_t len; + + if ((start = isolateValue(configFile, lineNum, key, startPtr, buf, length)) != NULL) { + + chptr = start; + + while( (len = mbrtowc(&pwc, chptr, strlen(chptr), NULL)) != 0 && strlen(chptr) != 0) { + if( len == (size_t)(-1) || len == (size_t)(-2) || !iswprint(pwc) || iswblank(pwc) ) { + message(MESS_ERROR, "%s:%d bad %s path %s\n", + configFile, lineNum, key, start); + return NULL; + } + chptr += len; + } + +/* + while (*chptr && isprint((unsigned char)*chptr) && *chptr != ' ') + chptr++; + if (*chptr) { + message(MESS_ERROR, "%s:%d bad %s path %s\n", + configFile, lineNum, key, start); + return NULL; + } +*/ + + path = strdup(start); + free(start); + + return path; + } else + return NULL; +} + +static int readModeUidGid(const char *configFile, int lineNum, char *key, + const char *directive, mode_t *mode, uid_t *uid, + gid_t *gid) { + char u[200], g[200]; + int m; + char tmp; + int rc; + struct group *group; + struct passwd *pw = NULL; + + if (!strcmp("su", directive)) + /* do not read <mode> for the 'su' directive */ + rc = 0; + else + rc = sscanf(key, "%o %199s %199s%c", &m, u, g, &tmp); + + /* We support 'key <owner> <group> notation now */ + if (rc == 0) { + rc = sscanf(key, "%199s %199s%c", u, g, &tmp); + /* Simulate that we have read mode and keep the default value. */ + if (rc > 0) { + m = *mode; + rc += 1; + } + } + + if (rc == 4) { + message(MESS_ERROR, "%s:%d extra arguments for " + "%s\n", configFile, lineNum, directive); + return -1; + } + + if (rc > 0) { + *mode = m; + } + + if (rc > 1) { + pw = getpwnam(u); + if (!pw) { + message(MESS_ERROR, "%s:%d unknown user '%s'\n", + configFile, lineNum, u); + return -1; + } + *uid = pw->pw_uid; + endpwent(); + } + if (rc > 2) { + group = getgrnam(g); + if (!group) { + message(MESS_ERROR, "%s:%d unknown group '%s'\n", + configFile, lineNum, g); + return -1; + } + *gid = group->gr_gid; + endgrent(); + } + + return 0; +} + +static char *readAddress(const char *configFile, int lineNum, char *key, + char **startPtr, char **buf, size_t length) +{ + char *endtag, *chptr; + char *start = *startPtr; + char *address; + + if ((endtag = isolateValue(configFile, lineNum, key, startPtr, buf, length)) != NULL) { + + chptr = endtag; + while (*chptr && isprint((unsigned char)*chptr) && *chptr != ' ') { + chptr++; + } + + if (*chptr) { + message(MESS_ERROR, "%s:%d bad %s address %s\n", + configFile, lineNum, key, start); + return NULL; + } + + address = strdup(endtag); + + free(endtag); + + return address; + } else + return NULL; +} + +static int do_mkdir(const char *path, mode_t mode, uid_t uid, gid_t gid) { + struct stat sb; + + if (stat(path, &sb) != 0) { + if (mkdir(path, mode) != 0 && errno != EEXIST) { + message(MESS_ERROR, "error creating %s: %s\n", + path, strerror(errno)); + return -1; + } + if (chown(path, uid, gid) != 0) { + message(MESS_ERROR, "error setting owner of %s to uid %d and gid %d: %s\n", + path, uid, gid, strerror(errno)); + return -1; + } + if (chmod(path, mode) != 0) { + message(MESS_ERROR, "error setting permissions of %s to 0%o: %s\n", + path, mode, strerror(errno)); + return -1; + } + } + else if (!S_ISDIR(sb.st_mode)) { + message(MESS_ERROR, "path %s already exists, but it is not a directory\n", + path); + errno = ENOTDIR; + return -1; + } + + return 0; +} + +static int mkpath(const char *path, mode_t mode, uid_t uid, gid_t gid) { + char *pp; + char *sp; + int rv; + char *copypath = strdup(path); + + rv = 0; + pp = copypath; + while (rv == 0 && (sp = strchr(pp, '/')) != 0) { + if (sp != pp) { + *sp = '\0'; + rv = do_mkdir(copypath, mode, uid, gid); + *sp = '/'; + } + pp = sp + 1; + } + if (rv == 0) { + rv = do_mkdir(path, mode, uid, gid); + } + free(copypath); + return rv; +} + +static int checkFile(const char *fname) +{ + int i; + char *pattern; + + /* Check if fname is '.' or '..'; if so, return false */ + if (fname[0] == '.' && (!fname[1] || (fname[1] == '.' && !fname[2]))) + return 0; + + /* Check if fname is ending in a taboo-extension; if so, return false */ + for (i = 0; i < tabooCount; i++) { + if (asprintf(&pattern, "*%s", tabooExts[i]) < 0) { + message(MESS_FATAL, "failed to allocate taboo pattern memory\n"); + } + if (!fnmatch(pattern, fname, 0)) + { + free(pattern); + message(MESS_DEBUG, "Ignoring %s, because of %s ending\n", + fname, tabooExts[i]); + return 0; + } + } + free(pattern); + /* All checks have been passed; return true */ + return 1; +} + +/* Used by qsort to sort filelist */ +static int compar(const void *p, const void *q) +{ + return strcoll(*((char **) p), *((char **) q)); +} + +/* Free memory blocks pointed to by pointers in a 2d array and the array itself */ +static void free_2d_array(char **array, int lines_count) +{ + int i; + for (i = 0; i < lines_count; ++i) + free(array[i]); + free(array); +} + +static void copyLogInfo(struct logInfo *to, struct logInfo *from) +{ + memset(to, 0, sizeof(*to)); + if (from->oldDir) + to->oldDir = strdup(from->oldDir); + to->criterium = from->criterium; + to->threshhold = from->threshhold; + to->minsize = from->minsize; + to->maxsize = from->maxsize; + to->rotateCount = from->rotateCount; + to->rotateMinAge = from->rotateMinAge; + to->rotateAge = from->rotateAge; + to->logStart = from->logStart; + if (from->pre) + to->pre = strdup(from->pre); + if (from->post) + to->post = strdup(from->post); + if (from->first) + to->first = strdup(from->first); + if (from->last) + to->last = strdup(from->last); + if (from->preremove) + to->preremove = strdup(from->preremove); + if (from->logAddress) + to->logAddress = strdup(from->logAddress); + if (from->extension) + to->extension = strdup(from->extension); + if (from->compress_prog) + to->compress_prog = strdup(from->compress_prog); + if (from->uncompress_prog) + to->uncompress_prog = strdup(from->uncompress_prog); + if (from->compress_ext) + to->compress_ext = strdup(from->compress_ext); + to->flags = from->flags; + to->shred_cycles = from->shred_cycles; + to->createMode = from->createMode; + to->createUid = from->createUid; + to->createGid = from->createGid; + to->suUid = from->suUid; + to->suGid = from->suGid; + to->olddirMode = from->olddirMode; + to->olddirUid = from->olddirUid; + to->olddirGid = from->olddirGid; + if (from->compress_options_count) { + poptDupArgv(from->compress_options_count, from->compress_options_list, + &to->compress_options_count, &to->compress_options_list); + } + if (from->dateformat) + to->dateformat = strdup(from->dateformat); +} + +static void freeLogInfo(struct logInfo *log) +{ + free(log->pattern); + free_2d_array(log->files, log->numFiles); + free(log->oldDir); + free(log->pre); + free(log->post); + free(log->first); + free(log->last); + free(log->preremove); + free(log->logAddress); + free(log->extension); + free(log->compress_prog); + free(log->uncompress_prog); + free(log->compress_ext); + free(log->compress_options_list); + free(log->dateformat); +} + +static struct logInfo *newLogInfo(struct logInfo *template) +{ + struct logInfo *new; + + if ((new = malloc(sizeof(*new))) == NULL) + return NULL; + + copyLogInfo(new, template); + TAILQ_INSERT_TAIL(&logs, new, list); + numLogs++; + + return new; +} + +static void removeLogInfo(struct logInfo *log) +{ + if (log == NULL) + return; + + freeLogInfo(log); + TAILQ_REMOVE(&logs, log, list); + numLogs--; +} + +static void freeTailLogs(int num) +{ + message(MESS_DEBUG, "removing last %d log configs\n", num); + + while (num--) + removeLogInfo(TAILQ_LAST(&logs, logInfoHead)); + +} + +static int readConfigPath(const char *path, struct logInfo *defConfig) +{ + struct stat sb; + int here, oldnumlogs, result = 1; + struct logInfo defConfigBackup; + + if (stat(path, &sb)) { + message(MESS_ERROR, "cannot stat %s: %s\n", path, strerror(errno)); + return 1; + } + + if (S_ISDIR(sb.st_mode)) { + char **namelist, **p; + struct dirent *dp; + int files_count, i; + DIR *dirp; + + here = open(".", O_RDONLY); + + if ((dirp = opendir(path)) == NULL) { + message(MESS_ERROR, "cannot open directory %s: %s\n", path, + strerror(errno)); + close(here); + return 1; + } + files_count = 0; + namelist = NULL; + while ((dp = readdir(dirp)) != NULL) { + if (checkFile(dp->d_name)) { + /* Realloc memory for namelist array if necessary */ + if (files_count % REALLOC_STEP == 0) { + p = (char **) realloc(namelist, + (files_count + + REALLOC_STEP) * sizeof(char *)); + if (p) { + namelist = p; + memset(namelist + files_count, '\0', + REALLOC_STEP * sizeof(char *)); + } else { + free_2d_array(namelist, files_count); + closedir(dirp); + close(here); + message(MESS_ERROR, "cannot realloc: %s\n", + strerror(errno)); + return 1; + } + } + /* Alloc memory for file name */ + if ((namelist[files_count] = + (char *) malloc(strlen(dp->d_name) + 1))) { + strcpy(namelist[files_count], dp->d_name); + files_count++; + } else { + free_2d_array(namelist, files_count); + closedir(dirp); + close(here); + message(MESS_ERROR, "cannot realloc: %s\n", + strerror(errno)); + return 1; + } + } + } + closedir(dirp); + + if (files_count > 0) { + qsort(namelist, files_count, sizeof(char *), compar); + } else { + close(here); + return 0; + } + + if (chdir(path)) { + message(MESS_ERROR, "error in chdir(\"%s\"): %s\n", path, + strerror(errno)); + close(here); + free_2d_array(namelist, files_count); + return 1; + } + + for (i = 0; i < files_count; ++i) { + assert(namelist[i] != NULL); + oldnumlogs = numLogs; + copyLogInfo(&defConfigBackup, defConfig); + if (readConfigFile(namelist[i], defConfig)) { + message(MESS_ERROR, "found error in file %s, skipping\n", namelist[i]); + freeTailLogs(numLogs - oldnumlogs); + freeLogInfo(defConfig); + copyLogInfo(defConfig, &defConfigBackup); + freeLogInfo(&defConfigBackup); + continue; + } else { + result = 0; + } + freeLogInfo(&defConfigBackup); + } + + if (fchdir(here) < 0) { + message(MESS_ERROR, "could not change directory to '.'"); + } + close(here); + free_2d_array(namelist, files_count); + } else { + oldnumlogs = numLogs; + copyLogInfo(&defConfigBackup, defConfig); + if (readConfigFile(path, defConfig)) { + freeTailLogs(numLogs - oldnumlogs); + freeLogInfo(defConfig); + copyLogInfo(defConfig, &defConfigBackup); + } else { + result = 0; + } + freeLogInfo(&defConfigBackup); + } + + return result; +} + +int readAllConfigPaths(const char **paths) +{ + int i, result = 0; + const char **file; + struct logInfo defConfig = { + .pattern = NULL, + .files = NULL, + .numFiles = 0, + .oldDir = NULL, + .criterium = ROT_SIZE, + .threshhold = 1024 * 1024, + .minsize = 0, + .maxsize = 0, + .rotateCount = 0, + .rotateMinAge = 0, + .rotateAge = 0, + .logStart = -1, + .pre = NULL, + .post = NULL, + .first = NULL, + .last = NULL, + .preremove = NULL, + .logAddress = NULL, + .extension = NULL, + .addextension = NULL, + .compress_prog = NULL, + .uncompress_prog = NULL, + .compress_ext = NULL, + .dateformat = NULL, + .flags = LOG_FLAG_IFEMPTY, + .shred_cycles = 0, + .createMode = NO_MODE, + .createUid = NO_UID, + .createGid = NO_GID, + .olddirMode = NO_MODE, + .olddirUid = NO_UID, + .olddirGid = NO_GID, + .suUid = NO_UID, + .suGid = NO_GID, + .compress_options_list = NULL, + .compress_options_count = 0 + }; + + tabooExts = malloc(sizeof(*tabooExts) * defTabooCount); + for (i = 0; i < defTabooCount; i++) { + if ((tabooExts[i] = (char *) malloc(strlen(defTabooExts[i]) + 1))) { + strcpy(tabooExts[i], defTabooExts[i]); + tabooCount++; + } else { + free_2d_array(tabooExts, tabooCount); + message(MESS_ERROR, "cannot malloc: %s\n", strerror(errno)); + return 1; + } + } + + for (file = paths; *file; file++) { + if (readConfigPath(*file, &defConfig)) { + result = 1; + break; + } + } + free_2d_array(tabooExts, tabooCount); + freeLogInfo(&defConfig); + return result; +} + +static int globerr(const char *pathname, int theerr) +{ + /* A missing directory is not an error, so return 0 */ + if (theerr == ENOTDIR) + return 0; + + glob_errno = theerr; + + /* We want the glob operation to abort on error, so return 1 */ + return 1; +} + +#define freeLogItem(what) \ + do { \ + free(newlog->what); \ + newlog->what = NULL; \ + } while (0); +#define RAISE_ERROR() \ + if (newlog != defConfig) { \ + state = STATE_ERROR; \ + continue; \ + } else { \ + goto error; \ + } +#define MAX_NESTING 16U + +static int readConfigFile(const char *configFile, struct logInfo *defConfig) +{ + int fd; + char *buf, *endtag, *key = NULL; + off_t length; + int lineNum = 1; + unsigned long long multiplier; + int i, k; + char *scriptStart = NULL; + char **scriptDest = NULL; + struct logInfo *newlog = defConfig; + char *start, *chptr; + char *dirName; + struct passwd *pw = NULL; + int rc; + struct stat sb, sb2; + glob_t globResult; + const char **argv; + int argc, argNum; + int flags; + int state = STATE_DEFAULT; + int logerror = 0; + struct logInfo *log; + static unsigned recursion_depth = 0U; + char *globerr_msg = NULL; + int in_config = 0; + int rv; + struct flock fd_lock = { + .l_start = 0, + .l_len = 0, + .l_whence = SEEK_SET, + .l_type = F_RDLCK + }; + + /* FIXME: createOwner and createGroup probably shouldn't be fixed + length arrays -- of course, if we aren't run setuid it doesn't + matter much */ + + fd = open(configFile, O_RDONLY); + if (fd < 0) { + message(MESS_ERROR, "failed to open config file %s: %s\n", + configFile, strerror(errno)); + return 1; + } + if ((flags = fcntl(fd, F_GETFD)) == -1) { + message(MESS_ERROR, "Could not retrieve flags from file %s\n", + configFile); + close(fd); + return 1; + } + flags |= FD_CLOEXEC; + if (fcntl(fd, F_SETFD, flags) == -1) { + message(MESS_ERROR, "Could not set flags on file %s\n", + configFile); + close(fd); + return 1; + } + /* We don't want anybody to change the file while we parse it, + * let's try to lock it for reading. */ + if (fcntl(fd, F_SETLK, &fd_lock) == -1) { + message(MESS_ERROR, "Could not lock file %s for reading\n", + configFile); + } + if (fstat(fd, &sb)) { + message(MESS_ERROR, "fstat of %s failed: %s\n", configFile, + strerror(errno)); + close(fd); + return 1; + } + if (!S_ISREG(sb.st_mode)) { + message(MESS_DEBUG, + "Ignoring %s because it's not a regular file.\n", + configFile); + close(fd); + return 0; + } + + if (!(pw = getpwuid(getuid()))) { + message(MESS_ERROR, "Logrotate UID is not in passwd file.\n"); + close(fd); + return 1; + } + + if (getuid() == ROOT_UID) { + if ((sb.st_mode & 07533) != 0400) { + message(MESS_ERROR, + "Ignoring %s because of bad file mode - must be 0644 or 0444.\n", + configFile); + close(fd); + return 0; + } + + if ((pw = getpwuid(ROOT_UID)) == NULL) { + message(MESS_DEBUG, + "Ignoring %s because there's no password entry for the owner.\n", + configFile); + close(fd); + return 0; + } + + if (sb.st_uid != ROOT_UID && (pw == NULL || + sb.st_uid != pw->pw_uid || + pw->pw_uid != ROOT_UID)) { + message(MESS_DEBUG, + "Ignoring %s because the file owner is wrong (should be root or user with uid 0).\n", + configFile); + close(fd); + return 0; + } + } + + length = sb.st_size; + + if (length > 0xffffff) { + message(MESS_ERROR, "file %s too large, probably not a config file.\n", + configFile); + close(fd); + return 1; + } + + /* We can't mmap empty file... */ + if (length == 0) { + message(MESS_DEBUG, + "Ignoring %s because it's empty.\n", + configFile); + close(fd); + return 0; + } + +#ifdef MAP_POPULATE + buf = mmap(NULL, (size_t) length, PROT_READ, + MAP_PRIVATE | MAP_POPULATE, fd, (off_t) 0); +#else /* MAP_POPULATE */ + buf = mmap(NULL, (size_t) length, PROT_READ, + MAP_PRIVATE, fd, (off_t) 0); +#endif /* MAP_POPULATE */ + + if (buf == MAP_FAILED) { + message(MESS_ERROR, "Error mapping config file %s: %s\n", + configFile, strerror(errno)); + close(fd); + return 1; + } + +#ifdef HAVE_MADVISE +#ifdef MADV_DONTFORK + madvise(buf, (size_t)(length + 2), + MADV_SEQUENTIAL | MADV_WILLNEED | MADV_DONTFORK); +#else /* MADV_DONTFORK */ + madvise(buf, (size_t)(length + 2), + MADV_SEQUENTIAL | MADV_WILLNEED); +#endif /* MADV_DONTFORK */ +#endif /* HAVE_MADVISE */ + + message(MESS_DEBUG, "reading config file %s\n", configFile); + + start = buf; + for (start = buf; start - buf < length; start++) { + if (key) { + free(key); + key = NULL; + } + switch (state) { + case STATE_DEFAULT: + if (isblank((unsigned char)*start)) + continue; + /* Skip comment */ + if (*start == '#') { + state = STATE_SKIP_LINE; + continue; + } + + if (isalpha((unsigned char)*start)) { + if ((key = isolateWord(&start, &buf, length)) == NULL) + continue; + if (!strcmp(key, "compress")) { + newlog->flags |= LOG_FLAG_COMPRESS; + } else if (!strcmp(key, "nocompress")) { + newlog->flags &= ~LOG_FLAG_COMPRESS; + } else if (!strcmp(key, "compress")) { + newlog->flags |= LOG_FLAG_COMPRESS; + } else if (!strcmp(key, "nocompress")) { + newlog->flags &= ~LOG_FLAG_COMPRESS; + } else if (!strcmp(key, "delaycompress")) { + newlog->flags |= LOG_FLAG_DELAYCOMPRESS; + } else if (!strcmp(key, "nodelaycompress")) { + newlog->flags &= ~LOG_FLAG_DELAYCOMPRESS; + } else if (!strcmp(key, "shred")) { + newlog->flags |= LOG_FLAG_SHRED; + } else if (!strcmp(key, "noshred")) { + newlog->flags &= ~LOG_FLAG_SHRED; + } else if (!strcmp(key, "sharedscripts")) { + newlog->flags |= LOG_FLAG_SHAREDSCRIPTS; + } else if (!strcmp(key, "nosharedscripts")) { + newlog->flags &= ~LOG_FLAG_SHAREDSCRIPTS; + } else if (!strcmp(key, "copytruncate")) { + newlog->flags |= LOG_FLAG_COPYTRUNCATE; + } else if (!strcmp(key, "nocopytruncate")) { + newlog->flags &= ~LOG_FLAG_COPYTRUNCATE; + } else if (!strcmp(key, "renamecopy")) { + newlog->flags |= LOG_FLAG_TMPFILENAME; + } else if (!strcmp(key, "norenamecopy")) { + newlog->flags &= ~LOG_FLAG_TMPFILENAME; + } else if (!strcmp(key, "copy")) { + newlog->flags |= LOG_FLAG_COPY; + } else if (!strcmp(key, "nocopy")) { + newlog->flags &= ~LOG_FLAG_COPY; + } else if (!strcmp(key, "ifempty")) { + newlog->flags |= LOG_FLAG_IFEMPTY; + } else if (!strcmp(key, "notifempty")) { + newlog->flags &= ~LOG_FLAG_IFEMPTY; + } else if (!strcmp(key, "dateext")) { + newlog->flags |= LOG_FLAG_DATEEXT; + } else if (!strcmp(key, "nodateext")) { + newlog->flags &= ~LOG_FLAG_DATEEXT; + } else if (!strcmp(key, "dateyesterday")) { + newlog->flags |= LOG_FLAG_DATEYESTERDAY; + } else if (!strcmp(key, "dateformat")) { + freeLogItem(dateformat); + newlog->dateformat = isolateLine(&start, &buf, length); + if (newlog->dateformat == NULL) + continue; + } else if (!strcmp(key, "noolddir")) { + newlog->oldDir = NULL; + } else if (!strcmp(key, "mailfirst")) { + newlog->flags |= LOG_FLAG_MAILFIRST; + } else if (!strcmp(key, "maillast")) { + newlog->flags &= ~LOG_FLAG_MAILFIRST; + } else if (!strcmp(key, "su")) { + mode_t tmp_mode = NO_MODE; + free(key); + key = isolateLine(&start, &buf, length); + if (key == NULL) + continue; + + rv = readModeUidGid(configFile, lineNum, key, "su", + &tmp_mode, &newlog->suUid, + &newlog->suGid); + if (rv == -1) { + RAISE_ERROR(); + } + else if (tmp_mode != NO_MODE) { + message(MESS_ERROR, "%s:%d extra arguments for " + "su\n", configFile, lineNum); + RAISE_ERROR(); + } + + newlog->flags |= LOG_FLAG_SU; + } else if (!strcmp(key, "create")) { + free(key); + key = isolateLine(&start, &buf, length); + if (key == NULL) + continue; + + rv = readModeUidGid(configFile, lineNum, key, "create", + &newlog->createMode, &newlog->createUid, + &newlog->createGid); + if (rv == -1) { + RAISE_ERROR(); + } + + newlog->flags |= LOG_FLAG_CREATE; + } else if (!strcmp(key, "createolddir")) { + free(key); + key = isolateLine(&start, &buf, length); + if (key == NULL) + continue; + + rv = readModeUidGid(configFile, lineNum, key, "createolddir", + &newlog->olddirMode, &newlog->olddirUid, + &newlog->olddirGid); + if (rv == -1) { + RAISE_ERROR(); + } + + newlog->flags |= LOG_FLAG_OLDDIRCREATE; + } else if (!strcmp(key, "nocreateolddir")) { + newlog->flags &= ~LOG_FLAG_OLDDIRCREATE; + } else if (!strcmp(key, "nocreate")) { + newlog->flags &= ~LOG_FLAG_CREATE; + } else if (!strcmp(key, "size") || !strcmp(key, "minsize") || + !strcmp(key, "maxsize")) { + unsigned long long size = 0; + char *opt = key; + + if ((key = isolateValue(configFile, lineNum, opt, &start, + &buf, length)) != NULL) { + int l = strlen(key) - 1; + if (key[l] == 'k' || key[l] == 'K') { + key[l] = '\0'; + multiplier = 1024; + } else if (key[l] == 'M') { + key[l] = '\0'; + multiplier = 1024 * 1024; + } else if (key[l] == 'G') { + key[l] = '\0'; + multiplier = 1024 * 1024 * 1024; + } else if (!isdigit((unsigned char)key[l])) { + free(opt); + message(MESS_ERROR, "%s:%d unknown unit '%c'\n", + configFile, lineNum, key[l]); + RAISE_ERROR(); + } else { + multiplier = 1; + } + + size = multiplier * strtoull(key, &chptr, 0); + if (*chptr) { + message(MESS_ERROR, "%s:%d bad size '%s'\n", + configFile, lineNum, key); + free(opt); + RAISE_ERROR(); + } + if (!strncmp(opt, "size", 4)) { + newlog->criterium = ROT_SIZE; + newlog->threshhold = size; + } else if (!strncmp(opt, "maxsize", 7)) { + newlog->maxsize = size; + } else { + newlog->minsize = size; + } + free(opt); + } + else { + free(opt); + continue; + } + } else if (!strcmp(key, "shredcycles")) { + free(key); + if ((key = isolateValue(configFile, lineNum, "shred cycles", + &start, &buf, length)) != NULL) { + newlog->shred_cycles = strtoul(key, &chptr, 0); + if (*chptr || newlog->shred_cycles < 0) { + message(MESS_ERROR, "%s:%d bad shred cycles '%s'\n", + configFile, lineNum, key); + goto error; + } + } + else continue; + } else if (!strcmp(key, "hourly")) { + newlog->criterium = ROT_HOURLY; + } else if (!strcmp(key, "daily")) { + newlog->criterium = ROT_DAYS; + newlog->threshhold = 1; + } else if (!strcmp(key, "monthly")) { + newlog->criterium = ROT_MONTHLY; + } else if (!strcmp(key, "weekly")) { + newlog->criterium = ROT_WEEKLY; + } else if (!strcmp(key, "yearly")) { + newlog->criterium = ROT_YEARLY; + } else if (!strcmp(key, "rotate")) { + free(key); + if ((key = isolateValue + (configFile, lineNum, "rotate count", &start, + &buf, length)) != NULL) { + + newlog->rotateCount = strtoul(key, &chptr, 0); + if (*chptr || newlog->rotateCount < 0) { + message(MESS_ERROR, + "%s:%d bad rotation count '%s'\n", + configFile, lineNum, key); + RAISE_ERROR(); + } + } + else continue; + } else if (!strcmp(key, "start")) { + free(key); + if ((key = isolateValue + (configFile, lineNum, "start count", &start, + &buf, length)) != NULL) { + + newlog->logStart = strtoul(key, &chptr, 0); + if (*chptr || newlog->logStart < 0) { + message(MESS_ERROR, "%s:%d bad start count '%s'\n", + configFile, lineNum, key); + RAISE_ERROR(); + } + } + else continue; + } else if (!strcmp(key, "minage")) { + free(key); + if ((key = isolateValue + (configFile, lineNum, "minage count", &start, + &buf, length)) != NULL) { + newlog->rotateMinAge = strtoul(key, &chptr, 0); + if (*chptr || newlog->rotateMinAge < 0) { + message(MESS_ERROR, "%s:%d bad minimum age '%s'\n", + configFile, lineNum, start); + RAISE_ERROR(); + } + } + else continue; + } else if (!strcmp(key, "maxage")) { + free(key); + if ((key = isolateValue + (configFile, lineNum, "maxage count", &start, + &buf, length)) != NULL) { + newlog->rotateAge = strtoul(key, &chptr, 0); + if (*chptr || newlog->rotateAge < 0) { + message(MESS_ERROR, "%s:%d bad maximum age '%s'\n", + configFile, lineNum, start); + RAISE_ERROR(); + } + } + else continue; + } else if (!strcmp(key, "errors")) { + message(MESS_DEBUG, + "%s: %d: the errors directive is deprecated and no longer used.\n", + configFile, lineNum); + } else if (!strcmp(key, "mail")) { + freeLogItem(logAddress); + if (!(newlog->logAddress = readAddress(configFile, lineNum, + "mail", &start, &buf, length))) { + RAISE_ERROR(); + } + else continue; + } else if (!strcmp(key, "nomail")) { + freeLogItem(logAddress); + } else if (!strcmp(key, "missingok")) { + newlog->flags |= LOG_FLAG_MISSINGOK; + } else if (!strcmp(key, "nomissingok")) { + newlog->flags &= ~LOG_FLAG_MISSINGOK; + } else if (!strcmp(key, "prerotate")) { + freeLogItem (pre); + scriptStart = start; + scriptDest = &newlog->pre; + state = STATE_LOAD_SCRIPT; + } else if (!strcmp(key, "firstaction")) { + freeLogItem (first); + scriptStart = start; + scriptDest = &newlog->first; + state = STATE_LOAD_SCRIPT; + } else if (!strcmp(key, "postrotate")) { + freeLogItem (post); + scriptStart = start; + scriptDest = &newlog->post; + state = STATE_LOAD_SCRIPT; + } else if (!strcmp(key, "lastaction")) { + freeLogItem (last); + scriptStart = start; + scriptDest = &newlog->last; + state = STATE_LOAD_SCRIPT; + } else if (!strcmp(key, "preremove")) { + freeLogItem (preremove); + scriptStart = start; + scriptDest = &newlog->preremove; + state = STATE_LOAD_SCRIPT; + } else if (!strcmp(key, "tabooext")) { + if (newlog != defConfig) { + message(MESS_ERROR, + "%s:%d tabooext may not appear inside " + "of log file definition\n", configFile, + lineNum); + state = STATE_ERROR; + continue; + } + free(key); + if ((key = isolateValue(configFile, lineNum, "tabooext", &start, + &buf, length)) != NULL) { + endtag = key; + if (*endtag == '+') { + endtag++; + while (isspace((unsigned char)*endtag) && *endtag) + endtag++; + } else { + free_2d_array(tabooExts, tabooCount); + tabooCount = 0; + tabooExts = malloc(1); + } + + + while (*endtag) { + chptr = endtag; + while (!isspace((unsigned char)*chptr) && *chptr != ',' && *chptr) + chptr++; + + tabooExts = realloc(tabooExts, sizeof(*tabooExts) * + (tabooCount + 1)); + tabooExts[tabooCount] = malloc(chptr - endtag + 1); + strncpy(tabooExts[tabooCount], endtag, + chptr - endtag); + tabooExts[tabooCount][chptr - endtag] = '\0'; + tabooCount++; + + endtag = chptr; + if (*endtag == ',') + endtag++; + while (*endtag && isspace((unsigned char)*endtag)) + endtag++; + } + } + else continue; + } else if (!strcmp(key, "include")) { + free(key); + if ((key = isolateValue(configFile, lineNum, "include", &start, + &buf, length)) != NULL) { + + message(MESS_DEBUG, "including %s\n", key); + if (++recursion_depth > MAX_NESTING) { + message(MESS_ERROR, "%s:%d include nesting too deep\n", + configFile, lineNum); + --recursion_depth; + goto error; + } + if (readConfigPath(key, newlog)) { + --recursion_depth; + goto error; + } + --recursion_depth; + } + else continue; + } else if (!strcmp(key, "olddir")) { + freeLogItem (oldDir); + + if (!(newlog->oldDir = readPath(configFile, lineNum, + "olddir", &start, &buf, length))) { + RAISE_ERROR(); + } + message(MESS_DEBUG, "olddir is now %s\n", newlog->oldDir); + } else if (!strcmp(key, "extension")) { + if ((key = isolateValue + (configFile, lineNum, "extension name", &start, + &buf, length)) != NULL) { + freeLogItem (extension); + newlog->extension = key; + key = NULL; + } + else continue; + + message(MESS_DEBUG, "extension is now %s\n", + newlog->extension); + + } else if (!strcmp(key, "addextension")) { + if ((key = isolateValue + (configFile, lineNum, "addextension name", &start, + &buf, length)) != NULL) { + freeLogItem (addextension); + newlog->addextension = key; + key = NULL; + } + else continue; + + message(MESS_DEBUG, "addextension is now %s\n", + newlog->addextension); + + } else if (!strcmp(key, "compresscmd")) { + freeLogItem (compress_prog); + + if (! + (newlog->compress_prog = + readPath(configFile, lineNum, "compress", &start, &buf, length))) { + RAISE_ERROR(); + } + + if (access(newlog->compress_prog, X_OK)) { + message(MESS_ERROR, + "%s:%d compression program %s is not an executable file\n", + configFile, lineNum, newlog->compress_prog); + RAISE_ERROR(); + } + + message(MESS_DEBUG, "compress_prog is now %s\n", + newlog->compress_prog); + + } else if (!strcmp(key, "uncompresscmd")) { + freeLogItem (uncompress_prog); + + if (! + (newlog->uncompress_prog = + readPath(configFile, lineNum, "uncompress", + &start, &buf, length))) { + RAISE_ERROR(); + } + + if (access(newlog->uncompress_prog, X_OK)) { + message(MESS_ERROR, + "%s:%d uncompression program %s is not an executable file\n", + configFile, lineNum, newlog->uncompress_prog); + RAISE_ERROR(); + } + + message(MESS_DEBUG, "uncompress_prog is now %s\n", + newlog->uncompress_prog); + + } else if (!strcmp(key, "compressoptions")) { + char *options; + + if (newlog->compress_options_list) { + free(newlog->compress_options_list); + newlog->compress_options_list = NULL; + newlog->compress_options_count = 0; + } + + if (!(options = isolateLine(&start, &buf, length))) { + RAISE_ERROR(); + } + + if (poptParseArgvString(options, + &newlog->compress_options_count, + &newlog->compress_options_list)) { + message(MESS_ERROR, + "%s:%d invalid compression options\n", + configFile, lineNum); + free(options); + RAISE_ERROR(); + } + + message(MESS_DEBUG, "compress_options is now %s\n", + options); + free(options); + } else if (!strcmp(key, "compressext")) { + freeLogItem (compress_ext); + + if (! + (newlog->compress_ext = + readPath(configFile, lineNum, "compress-ext", + &start, &buf, length))) { + RAISE_ERROR(); + } + + message(MESS_DEBUG, "compress_ext is now %s\n", + newlog->compress_ext); + } else { + message(MESS_ERROR, "%s:%d unknown option '%s' " + "-- ignoring line\n", configFile, lineNum, key); + if (*start != '\n') + state = STATE_SKIP_LINE; + } + free(key); + key = NULL; + } else if (*start == '/' || *start == '"' || *start == '\'' +#ifdef GLOB_TILDE + || *start == '~' +#endif + ) { + in_config = 0; + if (newlog != defConfig) { + message(MESS_ERROR, "%s:%d unexpected log filename\n", + configFile, lineNum); + state = STATE_ERROR; + continue; + } + + /* If no compression options were found in config file, set + default values */ + if (!newlog->compress_prog) + newlog->compress_prog = strdup(COMPRESS_COMMAND); + if (!newlog->uncompress_prog) + newlog->uncompress_prog = strdup(UNCOMPRESS_COMMAND); + if (!newlog->compress_ext) + newlog->compress_ext = strdup(COMPRESS_EXT); + + /* Allocate a new logInfo structure and insert it into the logs + queue, copying the actual values from defConfig */ + if ((newlog = newLogInfo(defConfig)) == NULL) + goto error; + + endtag = start; + while (endtag - buf < length && *endtag != '{' && *endtag != '}' && *endtag != '\0') { + endtag++;} + if (endtag - buf > length) + continue; + if (*endtag == '}') { + message(MESS_ERROR, "%s:%d unexpected } (missing previous '{')\n", configFile, + lineNum); + goto error; + } + if (*endtag == '{') { + in_config = 1; + } + else { + message(MESS_ERROR, "%s:%d missing '{' after log files definition\n", configFile, + lineNum); + goto error; + } + char *key = strndup(start, endtag - start); + start = endtag; + + if (poptParseArgvString(key, &argc, &argv)) { + message(MESS_ERROR, "%s:%d error parsing filename\n", + configFile, lineNum); + free(key); + goto error; + } else if (argc < 1) { + message(MESS_ERROR, + "%s:%d { expected after log file name(s)\n", + configFile, lineNum); + free(key); + goto error; + } + + newlog->files = NULL; + newlog->numFiles = 0; + for (argNum = 0; argNum < argc && logerror != 1; argNum++) { + if (globerr_msg) { + free(globerr_msg); + globerr_msg = NULL; + } + + rc = glob(argv[argNum], GLOB_NOCHECK +#ifdef GLOB_TILDE + | GLOB_TILDE +#endif + , globerr, &globResult); + if (rc == GLOB_ABORTED) { + if (newlog->flags & LOG_FLAG_MISSINGOK) { + continue; + } + + /* We don't yet know whether this stanza has "missingok" + * set, so store the error message for later. */ + rc = asprintf(&globerr_msg, "%s:%d glob failed for %s: %s\n", + configFile, lineNum, argv[argNum], strerror(glob_errno)); + if (rc == -1) + globerr_msg = NULL; + + globResult.gl_pathc = 0; + } + + newlog->files = + realloc(newlog->files, + sizeof(*newlog->files) * (newlog->numFiles + + globResult. + gl_pathc)); + + for (i = 0; i < globResult.gl_pathc; i++) { + /* if we glob directories we can get false matches */ + if (!lstat(globResult.gl_pathv[i], &sb) && + S_ISDIR(sb.st_mode)) { + continue; + } + + for (log = logs.tqh_first; log != NULL; + log = log->list.tqe_next) { + for (k = 0; k < log->numFiles; k++) { + if (!strcmp(log->files[k], + globResult.gl_pathv[i])) { + message(MESS_ERROR, + "%s:%d duplicate log entry for %s\n", + configFile, lineNum, + globResult.gl_pathv[i]); + logerror = 1; + goto duperror; + } + } + } + + newlog->files[newlog->numFiles] = + strdup(globResult.gl_pathv[i]); + newlog->numFiles++; + } + duperror: + globfree(&globResult); + } + + newlog->pattern = key; + + free(argv); + + } else if (*start == '}') { + if (newlog == defConfig) { + message(MESS_ERROR, "%s:%d unexpected }\n", configFile, + lineNum); + goto error; + } + if (!in_config) { + message(MESS_ERROR, "%s:%d unexpected } (missing previous '{')\n", configFile, + lineNum); + goto error; + } + in_config = 0; + if (globerr_msg) { + if (!(newlog->flags & LOG_FLAG_MISSINGOK)) + message(MESS_ERROR, "%s", globerr_msg); + free(globerr_msg); + globerr_msg = NULL; + if (!(newlog->flags & LOG_FLAG_MISSINGOK)) + goto error; + } + + if (newlog->oldDir) { + for (i = 0; i < newlog->numFiles; i++) { + char *ld; + int rv; + dirName = ourDirName(newlog->files[i]); + if (stat(dirName, &sb2)) { + if (!(newlog->flags & LOG_FLAG_MISSINGOK)) { + message(MESS_ERROR, + "%s:%d error verifying log file " + "path %s: %s\n", configFile, lineNum, + dirName, strerror(errno)); + free(dirName); + goto error; + } + else { + message(MESS_DEBUG, + "%s:%d verifying log file " + "path failed %s: %s, log is probably missing, " + "but missingok is set, so this is not an error.\n", + configFile, lineNum, + dirName, strerror(errno)); + free(dirName); + continue; + } + } + ld = alloca(strlen(dirName) + strlen(newlog->oldDir) + 2); + sprintf(ld, "%s/%s", dirName, newlog->oldDir); + free(dirName); + + if (newlog->oldDir[0] != '/') { + dirName = ld; + } + else { + dirName = newlog->oldDir; + } + + rv = stat(dirName, &sb); + if (rv) { + if (errno == ENOENT && newlog->flags & LOG_FLAG_OLDDIRCREATE) { + if (mkpath(dirName, newlog->olddirMode, + newlog->olddirUid, newlog->olddirGid)) { + goto error; + } + } + else { + message(MESS_ERROR, "%s:%d error verifying olddir " + "path %s: %s\n", configFile, lineNum, + dirName, strerror(errno)); + goto error; + } + } + + if (sb.st_dev != sb2.st_dev + && !(newlog->flags & (LOG_FLAG_COPYTRUNCATE | LOG_FLAG_COPY | LOG_FLAG_TMPFILENAME))) { + message(MESS_ERROR, + "%s:%d olddir %s and log file %s " + "are on different devices\n", configFile, + lineNum, newlog->oldDir, newlog->files[i]); + goto error; + } + } + } + + newlog = defConfig; + state = STATE_DEFINITION_END; + } else if (*start != '\n') { + message(MESS_ERROR, "%s:%d lines must begin with a keyword " + "or a filename (possibly in double quotes)\n", + configFile, lineNum); + state = STATE_SKIP_LINE; + } + break; + case STATE_SKIP_LINE: + case STATE_SKIP_LINE | STATE_SKIP_CONFIG: + if (*start == '\n') + state = state & STATE_SKIP_CONFIG ? STATE_SKIP_CONFIG : STATE_DEFAULT; + break; + case STATE_SKIP_LINE | STATE_LOAD_SCRIPT: + if (*start == '\n') + state = STATE_LOAD_SCRIPT; + break; + case STATE_SKIP_LINE | STATE_LOAD_SCRIPT | STATE_SKIP_CONFIG: + if (*start == '\n') + state = STATE_LOAD_SCRIPT | STATE_SKIP_CONFIG; + break; + case STATE_DEFINITION_END: + case STATE_DEFINITION_END | STATE_SKIP_CONFIG: + if (isblank((unsigned char)*start)) + continue; + if (*start != '\n') { + message(MESS_ERROR, "%s:%d, unexpected text after }\n", + configFile, lineNum); + state = STATE_SKIP_LINE | (state & STATE_SKIP_CONFIG ? STATE_SKIP_CONFIG : 0); + } + else + state = state & STATE_SKIP_CONFIG ? STATE_SKIP_CONFIG : STATE_DEFAULT; + break; + case STATE_ERROR: + assert(newlog != defConfig); + + message(MESS_ERROR, "found error in %s, skipping\n", + newlog->pattern ? newlog->pattern : "log config"); + + state = STATE_SKIP_CONFIG; + break; + case STATE_LOAD_SCRIPT: + case STATE_LOAD_SCRIPT | STATE_SKIP_CONFIG: + if ((key = isolateWord(&start, &buf, length)) == NULL) + continue; + + if (strcmp(key, "endscript") == 0) { + if (state & STATE_SKIP_CONFIG) { + state = STATE_SKIP_CONFIG; + } + else { + endtag = start - 9; + while (*endtag != '\n') + endtag--; + endtag++; + *scriptDest = malloc(endtag - scriptStart + 1); + strncpy(*scriptDest, scriptStart, + endtag - scriptStart); + (*scriptDest)[endtag - scriptStart] = '\0'; + + scriptDest = NULL; + scriptStart = NULL; + } + state = state & STATE_SKIP_CONFIG ? STATE_SKIP_CONFIG : STATE_DEFAULT; + } + else { + state = (*start == '\n' ? 0 : STATE_SKIP_LINE) | + STATE_LOAD_SCRIPT | + (state & STATE_SKIP_CONFIG ? STATE_SKIP_CONFIG : 0); + } + break; + case STATE_SKIP_CONFIG: + if (*start == '}') { + state = STATE_DEFAULT; + freeTailLogs(1); + newlog = defConfig; + } + else { + if ((key = isolateWord(&start, &buf, length)) == NULL) + continue; + if ( + (strcmp(key, "postrotate") == 0) || + (strcmp(key, "prerotate") == 0) || + (strcmp(key, "firstaction") == 0) || + (strcmp(key, "lastaction") == 0) || + (strcmp(key, "preremove") == 0) + ) { + state = STATE_LOAD_SCRIPT | STATE_SKIP_CONFIG; + } + else { + /* isolateWord moves the "start" pointer. + * If we have a line like + * rotate 5 + * after isolateWord "start" points to "5" and it + * is OK to skip the line, but if we have a line + * like the following + * nocompress + * after isolateWord "start" points to "\n". In + * this case if we skip a line, we skip the next + * line, not the current "nocompress" one, + * because in the for cycle the "start" + * pointer is increased by one and, after this, + * "start" points to the beginning of the next line. + */ + if (*start != '\n') { + state = STATE_SKIP_LINE | STATE_SKIP_CONFIG; + } + } + free(key); + key = NULL; + } + break; + } + if (key) { + free(key); + key = NULL; + } + if (*start == '\n') { + lineNum++; + } + + } + + if (scriptStart) { + message(MESS_ERROR, + "%s:prerotate, postrotate or preremove without endscript\n", + configFile); + goto error; + } + + munmap(buf, (size_t) length); + close(fd); + return 0; +error: + if (key) + free(key); + munmap(buf, (size_t) length); + close(fd); + return 1; +}
diff --git a/logrotate/config.h b/logrotate/config.h new file mode 100644 index 0000000..eed1174 --- /dev/null +++ b/logrotate/config.h
@@ -0,0 +1,51 @@ +/* + * OS-specific definitions + */ + +#define ROOT_UID 0 + +#ifdef __hpux +#define DEFAULT_MAIL_COMMAND "/usr/bin/mailx" +#define COMPRESS_COMMAND "/usr/contrib/bin/gzip" +#define UNCOMPRESS_COMMAND "/usr/contrib/bin/gunzip" +#define STATEFILE "/var/run/logrotate.status" +#endif + +#ifdef SunOS +#define DEFAULT_MAIL_COMMAND "/usr/bin/mailx" +#define STATEFILE "/var/log/logrotate.status" +#endif + +#ifdef __NetBSD__ +#define DEFAULT_MAIL_COMMAND "/usr/bin/mail -s" +#define COMPRESS_COMMAND "/usr/bin/gzip" +#define UNCOMPRESS_COMMAND "/usr/bin/gunzip" +#define STATEFILE "/var/log/logrotate.status" +#endif + +#if defined(__APPLE__) && defined(__MACH__) +#define COMPRESS_COMMAND "/usr/bin/gzip" +#define UNCOMPRESS_COMMAND "/usr/bin/gunzip" +#endif + +/* + * Default settings for Linux - leave these last. + */ +#ifndef DEFAULT_MAIL_COMMAND +#define DEFAULT_MAIL_COMMAND "" +#endif +#ifndef COMPRESS_COMMAND +#define COMPRESS_COMMAND "/system/bin/gzip" +#endif + +#ifndef COMPRESS_EXT +#define COMPRESS_EXT ".gz" +#endif + +#ifndef UNCOMPRESS_COMMAND +#define UNCOMPRESS_COMMAND "/system/bin/gunzip" +#endif + +#ifndef STATEFILE +#define STATEFILE "/data/misc/logrotate/logrotate.status" +#endif
diff --git a/logrotate/configure.ac b/logrotate/configure.ac new file mode 100644 index 0000000..7de899e --- /dev/null +++ b/logrotate/configure.ac
@@ -0,0 +1,52 @@ +AC_INIT([logrotate],[3.10.0]) + +dnl foreign: Do not require AUTHORS, ChangeLog, NEWS, and README to exist +dnl serial-tests: Do not hide standard output of our sequential test-suite +AM_INIT_AUTOMAKE([foreign serial-tests]) + +AC_DEFINE(_GNU_SOURCE) + +AM_EXTRA_RECURSIVE_TARGETS([test]) + +AC_PROG_CC +AC_PROG_CC_STDC +AC_STRUCT_ST_BLKSIZE +AC_STRUCT_ST_BLOCKS + +dnl Use 64-bit file offsets on 32-bit systems (defines C macros if necessary) +AC_SYS_LARGEFILE + +AC_CHECK_LIB([popt],[poptParseArgvString],, + AC_MSG_ERROR([libpopt required but not found])) + +dnl Needed for out-of-source builds +mkdir -p test + +AC_ARG_WITH([selinux], + [AS_HELP_STRING([--with-selinux], + [support handling SELinux contexts (yes,no,check) @<:@default=check@:>@])], + [], + [with_selinux=check]) +AS_CASE(["$with_selinux"], + [yes], [AC_CHECK_LIB([selinux],[getfscreatecon_raw])], + [no], [], + [AC_CHECK_LIB([selinux],[getfscreatecon_raw])]) +AS_IF([test "$ac_cv_lib_selinux_getfscreatecon_raw" = yes], + echo "1" > ./test/test.SELINUX;, echo "0" > ./test/test.SELINUX;) + +AC_ARG_WITH([acl], + [AS_HELP_STRING([--with-acl], + [support handling ACL (yes,no,check) @<:@default=check@:>@])], + [], + [with_acl=check]) +AS_CASE(["$with_acl"], + [yes], [AC_CHECK_LIB([acl],[acl_get_file])], + [no], [], + [AC_CHECK_LIB([acl],[acl_get_file])]) +AS_IF([test "$ac_cv_lib_acl_acl_get_file" = yes], + echo "1" > ./test/test.ACL;, echo "0" > ./test/test.ACL;) + +AC_CHECK_FUNCS([asprintf fork madvise qsort strndup strptime vfork vsyslog]) + +AC_CONFIG_FILES([Makefile test/Makefile logrotate.spec]) +AC_OUTPUT
diff --git a/logrotate/examples/logrotate-default b/logrotate/examples/logrotate-default new file mode 100644 index 0000000..56e9103 --- /dev/null +++ b/logrotate/examples/logrotate-default
@@ -0,0 +1,35 @@ +# see "man logrotate" for details +# rotate log files weekly +weekly + +# keep 4 weeks worth of backlogs +rotate 4 + +# create new (empty) log files after rotating old ones +create + +# use date as a suffix of the rotated file +dateext + +# uncomment this if you want your log files compressed +#compress + +# RPM packages drop log rotation information into this directory +include /etc/logrotate.d + +# no packages own wtmp and btmp -- we'll rotate them here +/var/log/wtmp { + monthly + create 0664 root utmp + minsize 1M + rotate 1 +} + +/var/log/btmp { + missingok + monthly + create 0600 root utmp + rotate 1 +} + +# system-specific logs may be also be configured here.
diff --git a/logrotate/examples/logrotate.cron b/logrotate/examples/logrotate.cron new file mode 100644 index 0000000..c6d50d4 --- /dev/null +++ b/logrotate/examples/logrotate.cron
@@ -0,0 +1,8 @@ +#!/bin/sh + +/usr/sbin/logrotate /etc/logrotate.conf +EXITVALUE=$? +if [ $EXITVALUE != 0 ]; then + /usr/bin/logger -t logrotate "ALERT exited abnormally with [$EXITVALUE]" +fi +exit 0
diff --git a/logrotate/examples/logrotate.service b/logrotate/examples/logrotate.service new file mode 100644 index 0000000..e276878 --- /dev/null +++ b/logrotate/examples/logrotate.service
@@ -0,0 +1,11 @@ +[Unit] +Description=Rotate log files +Documentation=man:logrotate(8) man:logrotate.conf(5) +ConditionACPower=true + +[Service] +Type=oneshot +ExecStart=/usr/sbin/logrotate /etc/logrotate.conf +Nice=19 +IOSchedulingClass=best-effort +IOSchedulingPriority=7
diff --git a/logrotate/examples/logrotate.timer b/logrotate/examples/logrotate.timer new file mode 100644 index 0000000..8f405d5 --- /dev/null +++ b/logrotate/examples/logrotate.timer
@@ -0,0 +1,11 @@ +[Unit] +Description=Daily rotation of log files +Documentation=man:logrotate(8) man:logrotate.conf(5) + +[Timer] +OnCalendar=daily +AccuracySec=12h +Persistent=true + +[Install] +WantedBy=timers.target
diff --git a/logrotate/glob.c b/logrotate/glob.c new file mode 100644 index 0000000..1418aa7 --- /dev/null +++ b/logrotate/glob.c
@@ -0,0 +1,905 @@ +/* + * Natanael Arndt, 2011: removed collate.h dependencies + * (my changes are trivial) + * + * Copyright (c) 1989, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Guido van Rossum. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#if defined(LIBC_SCCS) && !defined(lint) +static char sccsid[] = "@(#)glob.c 8.3 (Berkeley) 10/13/93"; +#endif /* LIBC_SCCS and not lint */ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +/* + * glob(3) -- a superset of the one defined in POSIX 1003.2. + * + * The [!...] convention to negate a range is supported (SysV, Posix, ksh). + * + * Optional extra services, controlled by flags not defined by POSIX: + * + * GLOB_QUOTE: + * Escaping convention: \ inhibits any special meaning the following + * character might have (except \ at end of string is retained). + * GLOB_MAGCHAR: + * Set in gl_flags if pattern contained a globbing character. + * GLOB_NOMAGIC: + * Same as GLOB_NOCHECK, but it will only append pattern if it did + * not contain any magic characters. [Used in csh style globbing] + * GLOB_ALTDIRFUNC: + * Use alternately specified directory access functions. + * GLOB_TILDE: + * expand ~user/foo to the /home/dir/of/user/foo + * GLOB_BRACE: + * expand {1,2}{a,b} to 1a 1b 2a 2b + * gl_matchc: + * Number of matches in the current invocation of glob. + */ + +/* + * Some notes on multibyte character support: + * 1. Patterns with illegal byte sequences match nothing - even if + * GLOB_NOCHECK is specified. + * 2. Illegal byte sequences in filenames are handled by treating them as + * single-byte characters with a value of the first byte of the sequence + * cast to wchar_t. + * 3. State-dependent encodings are not currently supported. + */ + +#include <sys/param.h> +#include <sys/stat.h> + +#include <ctype.h> +#include <dirent.h> +#include <errno.h> +#include <glob.h> +#include <limits.h> +#include <pwd.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <wchar.h> + +#define DOLLAR '$' +#define DOT '.' +#define EOS '\0' +#define LBRACKET '[' +#define NOT '!' +#define QUESTION '?' +#define QUOTE '\\' +#define RANGE '-' +#define RBRACKET ']' +#define SEP '/' +#define STAR '*' +#define TILDE '~' +#define UNDERSCORE '_' +#define LBRACE '{' +#define RBRACE '}' +#define SLASH '/' +#define COMMA ',' + +#ifndef DEBUG + +#define M_QUOTE 0x8000000000ULL +#define M_PROTECT 0x4000000000ULL +#define M_MASK 0xffffffffffULL +#define M_CHAR 0x00ffffffffULL + +typedef uint_fast64_t Char; + +#else + +#define M_QUOTE 0x80 +#define M_PROTECT 0x40 +#define M_MASK 0xff +#define M_CHAR 0x7f + +typedef char Char; + +#endif + + +#define CHAR(c) ((Char)((c)&M_CHAR)) +#define META(c) ((Char)((c)|M_QUOTE)) +#define M_ALL META('*') +#define M_END META(']') +#define M_NOT META('!') +#define M_ONE META('?') +#define M_RNG META('-') +#define M_SET META('[') +#define ismeta(c) (((c)&M_QUOTE) != 0) + + +static int compare(const void *, const void *); +static int g_Ctoc(const Char *, char *, size_t); +static int g_lstat(Char *, struct stat *, glob_t *); +static DIR *g_opendir(Char *, glob_t *); +static const Char *g_strchr(const Char *, wchar_t); +#ifdef notdef +static Char *g_strcat(Char *, const Char *); +#endif +static int g_stat(Char *, struct stat *, glob_t *); +static int glob0(const Char *, glob_t *, size_t *); +static int glob1(Char *, glob_t *, size_t *); +static int glob2(Char *, Char *, Char *, Char *, glob_t *, size_t *); +static int glob3(Char *, Char *, Char *, Char *, Char *, glob_t *, size_t *); +static int globextend(const Char *, glob_t *, size_t *); +static const Char * + globtilde(const Char *, Char *, size_t, glob_t *); +static int globexp1(const Char *, glob_t *, size_t *); +static int globexp2(const Char *, const Char *, glob_t *, int *, size_t *); +static int match(Char *, Char *, Char *); +#ifdef DEBUG +static void qprintf(const char *, Char *); +#endif + +int +glob(const char *pattern, int flags, int (*errfunc)(const char *, int), glob_t *pglob) +{ + const char *patnext; + size_t limit; + Char *bufnext, *bufend, patbuf[MAXPATHLEN], prot; + mbstate_t mbs; + wchar_t wc; + size_t clen; + + patnext = pattern; + if (!(flags & GLOB_APPEND)) { + pglob->gl_pathc = 0; + pglob->gl_pathv = NULL; + if (!(flags & GLOB_DOOFFS)) + pglob->gl_offs = 0; + } + if (flags & GLOB_LIMIT) { + limit = pglob->gl_matchc; + if (limit == 0) + limit = _SC_ARG_MAX; + } else + limit = 0; + pglob->gl_flags = flags & ~GLOB_MAGCHAR; + pglob->gl_errfunc = errfunc; + pglob->gl_matchc = 0; + + bufnext = patbuf; + bufend = bufnext + MAXPATHLEN - 1; + if (flags & GLOB_NOESCAPE) { + memset(&mbs, 0, sizeof(mbs)); + while (bufend - bufnext >= MB_CUR_MAX) { + clen = mbrtowc(&wc, patnext, MB_LEN_MAX, &mbs); + if (clen == (size_t)-1 || clen == (size_t)-2) + return (GLOB_NOMATCH); + else if (clen == 0) + break; + *bufnext++ = wc; + patnext += clen; + } + } else { + /* Protect the quoted characters. */ + memset(&mbs, 0, sizeof(mbs)); + while (bufend - bufnext >= MB_CUR_MAX) { + if (*patnext == QUOTE) { + if (*++patnext == EOS) { + *bufnext++ = QUOTE | M_PROTECT; + continue; + } + prot = M_PROTECT; + } else + prot = 0; + clen = mbrtowc(&wc, patnext, MB_LEN_MAX, &mbs); + if (clen == (size_t)-1 || clen == (size_t)-2) + return (GLOB_NOMATCH); + else if (clen == 0) + break; + *bufnext++ = wc | prot; + patnext += clen; + } + } + *bufnext = EOS; + + if (flags & GLOB_BRACE) + return globexp1(patbuf, pglob, &limit); + else + return glob0(patbuf, pglob, &limit); +} + +/* + * Expand recursively a glob {} pattern. When there is no more expansion + * invoke the standard globbing routine to glob the rest of the magic + * characters + */ +static int +globexp1(const Char *pattern, glob_t *pglob, size_t *limit) +{ + const Char* ptr = pattern; + int rv; + + /* Protect a single {}, for find(1), like csh */ + if (pattern[0] == LBRACE && pattern[1] == RBRACE && pattern[2] == EOS) + return glob0(pattern, pglob, limit); + + while ((ptr = g_strchr(ptr, LBRACE)) != NULL) + if (!globexp2(ptr, pattern, pglob, &rv, limit)) + return rv; + + return glob0(pattern, pglob, limit); +} + + +/* + * Recursive brace globbing helper. Tries to expand a single brace. + * If it succeeds then it invokes globexp1 with the new pattern. + * If it fails then it tries to glob the rest of the pattern and returns. + */ +static int +globexp2(const Char *ptr, const Char *pattern, glob_t *pglob, int *rv, size_t *limit) +{ + int i; + Char *lm, *ls; + const Char *pe, *pm, *pm1, *pl; + Char patbuf[MAXPATHLEN]; + + /* copy part up to the brace */ + for (lm = patbuf, pm = pattern; pm != ptr; *lm++ = *pm++) + continue; + *lm = EOS; + ls = lm; + + /* Find the balanced brace */ + for (i = 0, pe = ++ptr; *pe; pe++) + if (*pe == LBRACKET) { + /* Ignore everything between [] */ + for (pm = pe++; *pe != RBRACKET && *pe != EOS; pe++) + continue; + if (*pe == EOS) { + /* + * We could not find a matching RBRACKET. + * Ignore and just look for RBRACE + */ + pe = pm; + } + } + else if (*pe == LBRACE) + i++; + else if (*pe == RBRACE) { + if (i == 0) + break; + i--; + } + + /* Non matching braces; just glob the pattern */ + if (i != 0 || *pe == EOS) { + *rv = glob0(patbuf, pglob, limit); + return 0; + } + + for (i = 0, pl = pm = ptr; pm <= pe; pm++) + switch (*pm) { + case LBRACKET: + /* Ignore everything between [] */ + for (pm1 = pm++; *pm != RBRACKET && *pm != EOS; pm++) + continue; + if (*pm == EOS) { + /* + * We could not find a matching RBRACKET. + * Ignore and just look for RBRACE + */ + pm = pm1; + } + break; + + case LBRACE: + i++; + break; + + case RBRACE: + if (i) { + i--; + break; + } + /* FALLTHROUGH */ + case COMMA: + if (i && *pm == COMMA) + break; + else { + /* Append the current string */ + for (lm = ls; (pl < pm); *lm++ = *pl++) + continue; + /* + * Append the rest of the pattern after the + * closing brace + */ + for (pl = pe + 1; (*lm++ = *pl++) != EOS;) + continue; + + /* Expand the current pattern */ +#ifdef DEBUG + qprintf("globexp2:", patbuf); +#endif + *rv = globexp1(patbuf, pglob, limit); + + /* move after the comma, to the next string */ + pl = pm + 1; + } + break; + + default: + break; + } + *rv = 0; + return 0; +} + + + +/* + * expand tilde from the passwd file. + */ +static const Char * +globtilde(const Char *pattern, Char *patbuf, size_t patbuf_len, glob_t *pglob) +{ + struct passwd *pwd; + char *h; + const Char *p; + Char *b, *eb; + + if (*pattern != TILDE || !(pglob->gl_flags & GLOB_TILDE)) + return pattern; + + /* + * Copy up to the end of the string or / + */ + eb = &patbuf[patbuf_len - 1]; + for (p = pattern + 1, h = (char *) patbuf; + h < (char *)eb && *p && *p != SLASH; *h++ = *p++) + continue; + + *h = EOS; + + if (((char *) patbuf)[0] == EOS) { + /* + * handle a plain ~ or ~/ by expanding $HOME first (iff + * we're not running setuid or setgid) and then trying + * the password file + */ + if ((h = getenv("HOME")) == NULL) { + if (((h = getlogin()) != NULL && + (pwd = getpwnam(h)) != NULL) || + (pwd = getpwuid(getuid())) != NULL) + h = pwd->pw_dir; + else + return pattern; + } + } + else { + /* + * Expand a ~user + */ + if ((pwd = getpwnam((char*) patbuf)) == NULL) + return pattern; + else + h = pwd->pw_dir; + } + + /* Copy the home directory */ + for (b = patbuf; b < eb && *h; *b++ = *h++) + continue; + + /* Append the rest of the pattern */ + while (b < eb && (*b++ = *p++) != EOS) + continue; + *b = EOS; + + return patbuf; +} + + +/* + * The main glob() routine: compiles the pattern (optionally processing + * quotes), calls glob1() to do the real pattern matching, and finally + * sorts the list (unless unsorted operation is requested). Returns 0 + * if things went well, nonzero if errors occurred. + */ +static int +glob0(const Char *pattern, glob_t *pglob, size_t *limit) +{ + const Char *qpatnext; + int err; + size_t oldpathc; + Char *bufnext, c, patbuf[MAXPATHLEN]; + + qpatnext = globtilde(pattern, patbuf, MAXPATHLEN, pglob); + oldpathc = pglob->gl_pathc; + bufnext = patbuf; + + /* We don't need to check for buffer overflow any more. */ + while ((c = *qpatnext++) != EOS) { + switch (c) { + case LBRACKET: + c = *qpatnext; + if (c == NOT) + ++qpatnext; + if (*qpatnext == EOS || + g_strchr(qpatnext+1, RBRACKET) == NULL) { + *bufnext++ = LBRACKET; + if (c == NOT) + --qpatnext; + break; + } + *bufnext++ = M_SET; + if (c == NOT) + *bufnext++ = M_NOT; + c = *qpatnext++; + do { + *bufnext++ = CHAR(c); + if (*qpatnext == RANGE && + (c = qpatnext[1]) != RBRACKET) { + *bufnext++ = M_RNG; + *bufnext++ = CHAR(c); + qpatnext += 2; + } + } while ((c = *qpatnext++) != RBRACKET); + pglob->gl_flags |= GLOB_MAGCHAR; + *bufnext++ = M_END; + break; + case QUESTION: + pglob->gl_flags |= GLOB_MAGCHAR; + *bufnext++ = M_ONE; + break; + case STAR: + pglob->gl_flags |= GLOB_MAGCHAR; + /* collapse adjacent stars to one, + * to avoid exponential behavior + */ + if (bufnext == patbuf || bufnext[-1] != M_ALL) + *bufnext++ = M_ALL; + break; + default: + *bufnext++ = CHAR(c); + break; + } + } + *bufnext = EOS; +#ifdef DEBUG + qprintf("glob0:", patbuf); +#endif + + if ((err = glob1(patbuf, pglob, limit)) != 0) + return(err); + + /* + * If there was no match we are going to append the pattern + * if GLOB_NOCHECK was specified or if GLOB_NOMAGIC was specified + * and the pattern did not contain any magic characters + * GLOB_NOMAGIC is there just for compatibility with csh. + */ + if (pglob->gl_pathc == oldpathc) { + if (((pglob->gl_flags & GLOB_NOCHECK) || + ((pglob->gl_flags & GLOB_NOMAGIC) && + !(pglob->gl_flags & GLOB_MAGCHAR)))) + return(globextend(pattern, pglob, limit)); + else + return(GLOB_NOMATCH); + } + if (!(pglob->gl_flags & GLOB_NOSORT)) + qsort(pglob->gl_pathv + pglob->gl_offs + oldpathc, + pglob->gl_pathc - oldpathc, sizeof(char *), compare); + return(0); +} + +static int +compare(const void *p, const void *q) +{ + return(strcmp(*(char **)p, *(char **)q)); +} + +static int +glob1(Char *pattern, glob_t *pglob, size_t *limit) +{ + Char pathbuf[MAXPATHLEN]; + + /* A null pathname is invalid -- POSIX 1003.1 sect. 2.4. */ + if (*pattern == EOS) + return(0); + return(glob2(pathbuf, pathbuf, pathbuf + MAXPATHLEN - 1, + pattern, pglob, limit)); +} + +/* + * The functions glob2 and glob3 are mutually recursive; there is one level + * of recursion for each segment in the pattern that contains one or more + * meta characters. + */ +static int +glob2(Char *pathbuf, Char *pathend, Char *pathend_last, Char *pattern, + glob_t *pglob, size_t *limit) +{ + struct stat sb; + Char *p, *q; + int anymeta; + + /* + * Loop over pattern segments until end of pattern or until + * segment with meta character found. + */ + for (anymeta = 0;;) { + if (*pattern == EOS) { /* End of pattern? */ + *pathend = EOS; + if (g_lstat(pathbuf, &sb, pglob)) + return(0); + + if (((pglob->gl_flags & GLOB_MARK) && + pathend[-1] != SEP) && (S_ISDIR(sb.st_mode) + || (S_ISLNK(sb.st_mode) && + (g_stat(pathbuf, &sb, pglob) == 0) && + S_ISDIR(sb.st_mode)))) { + if (pathend + 1 > pathend_last) + return (GLOB_ABORTED); + *pathend++ = SEP; + *pathend = EOS; + } + ++pglob->gl_matchc; + return(globextend(pathbuf, pglob, limit)); + } + + /* Find end of next segment, copy tentatively to pathend. */ + q = pathend; + p = pattern; + while (*p != EOS && *p != SEP) { + if (ismeta(*p)) + anymeta = 1; + if (q + 1 > pathend_last) + return (GLOB_ABORTED); + *q++ = *p++; + } + + if (!anymeta) { /* No expansion, do next segment. */ + pathend = q; + pattern = p; + while (*pattern == SEP) { + if (pathend + 1 > pathend_last) + return (GLOB_ABORTED); + *pathend++ = *pattern++; + } + } else /* Need expansion, recurse. */ + return(glob3(pathbuf, pathend, pathend_last, pattern, p, + pglob, limit)); + } + /* NOTREACHED */ +} + +static int +glob3(Char *pathbuf, Char *pathend, Char *pathend_last, + Char *pattern, Char *restpattern, + glob_t *pglob, size_t *limit) +{ + struct dirent *dp; + DIR *dirp; + int err; + char buf[MAXPATHLEN]; + + /* + * The readdirfunc declaration can't be prototyped, because it is + * assigned, below, to two functions which are prototyped in glob.h + * and dirent.h as taking pointers to differently typed opaque + * structures. + */ + struct dirent *(*readdirfunc)(); + + if (pathend > pathend_last) + return (GLOB_ABORTED); + *pathend = EOS; + errno = 0; + + if ((dirp = g_opendir(pathbuf, pglob)) == NULL) { + /* TODO: don't call for ENOENT or ENOTDIR? */ + if (pglob->gl_errfunc) { + if (g_Ctoc(pathbuf, buf, sizeof(buf))) + return (GLOB_ABORTED); + if (pglob->gl_errfunc(buf, errno) || + pglob->gl_flags & GLOB_ERR) + return (GLOB_ABORTED); + } + return(0); + } + + err = 0; + + /* Search directory for matching names. */ + if (pglob->gl_flags & GLOB_ALTDIRFUNC) + readdirfunc = pglob->gl_readdir; + else + readdirfunc = readdir; + while ((dp = (*readdirfunc)(dirp))) { + char *sc; + Char *dc; + wchar_t wc; + size_t clen; + mbstate_t mbs; + + /* Initial DOT must be matched literally. */ + if (dp->d_name[0] == DOT && *pattern != DOT) + continue; + memset(&mbs, 0, sizeof(mbs)); + dc = pathend; + sc = dp->d_name; + while (dc < pathend_last) { + clen = mbrtowc(&wc, sc, MB_LEN_MAX, &mbs); + if (clen == (size_t)-1 || clen == (size_t)-2) { + wc = *sc; + clen = 1; + memset(&mbs, 0, sizeof(mbs)); + } + if ((*dc++ = wc) == EOS) + break; + sc += clen; + } + if (!match(pathend, pattern, restpattern)) { + *pathend = EOS; + continue; + } + err = glob2(pathbuf, --dc, pathend_last, restpattern, + pglob, limit); + if (err) + break; + } + + if (pglob->gl_flags & GLOB_ALTDIRFUNC) + (*pglob->gl_closedir)(dirp); + else + closedir(dirp); + return(err); +} + + +/* + * Extend the gl_pathv member of a glob_t structure to accomodate a new item, + * add the new item, and update gl_pathc. + * + * This assumes the BSD realloc, which only copies the block when its size + * crosses a power-of-two boundary; for v7 realloc, this would cause quadratic + * behavior. + * + * Return 0 if new item added, error code if memory couldn't be allocated. + * + * Invariant of the glob_t structure: + * Either gl_pathc is zero and gl_pathv is NULL; or gl_pathc > 0 and + * gl_pathv points to (gl_offs + gl_pathc + 1) items. + */ +static int +globextend(const Char *path, glob_t *pglob, size_t *limit) +{ + char **pathv; + size_t i, newsize, len; + char *copy; + const Char *p; + + if (*limit && pglob->gl_pathc > *limit) { + errno = 0; + return (GLOB_NOSPACE); + } + + newsize = sizeof(*pathv) * (2 + pglob->gl_pathc + pglob->gl_offs); + pathv = pglob->gl_pathv ? + realloc((char *)pglob->gl_pathv, newsize) : + malloc(newsize); + if (pathv == NULL) { + if (pglob->gl_pathv) { + free(pglob->gl_pathv); + pglob->gl_pathv = NULL; + } + return(GLOB_NOSPACE); + } + + if (pglob->gl_pathv == NULL && pglob->gl_offs > 0) { + /* first time around -- clear initial gl_offs items */ + pathv += pglob->gl_offs; + for (i = pglob->gl_offs + 1; --i > 0; ) + *--pathv = NULL; + } + pglob->gl_pathv = pathv; + + for (p = path; *p++;) + continue; + len = MB_CUR_MAX * (size_t)(p - path); /* XXX overallocation */ + if ((copy = malloc(len)) != NULL) { + if (g_Ctoc(path, copy, len)) { + free(copy); + return (GLOB_NOSPACE); + } + pathv[pglob->gl_offs + pglob->gl_pathc++] = copy; + } + pathv[pglob->gl_offs + pglob->gl_pathc] = NULL; + return(copy == NULL ? GLOB_NOSPACE : 0); +} + +/* + * pattern matching function for filenames. Each occurrence of the * + * pattern causes a recursion level. + */ +static int +match(Char *name, Char *pat, Char *patend) +{ + int ok, negate_range; + Char c, k; + + while (pat < patend) { + c = *pat++; + switch (c & M_MASK) { + case M_ALL: + if (pat == patend) + return(1); + do + if (match(name, pat, patend)) + return(1); + while (*name++ != EOS); + return(0); + case M_ONE: + if (*name++ == EOS) + return(0); + break; + case M_SET: + ok = 0; + if ((k = *name++) == EOS) + return(0); + if ((negate_range = ((*pat & M_MASK) == M_NOT)) != EOS) + ++pat; + while (((c = *pat++) & M_MASK) != M_END) + if ((*pat & M_MASK) == M_RNG) { + if (CHAR(c) <= CHAR(k) && CHAR(k) <= CHAR(pat[1])) ok = 1; + pat += 2; + } else if (c == k) + ok = 1; + if (ok == negate_range) + return(0); + break; + default: + if (*name++ != c) + return(0); + break; + } + } + return(*name == EOS); +} + +/* Free allocated data belonging to a glob_t structure. */ +void +globfree(glob_t *pglob) +{ + size_t i; + char **pp; + + if (pglob->gl_pathv != NULL) { + pp = pglob->gl_pathv + pglob->gl_offs; + for (i = pglob->gl_pathc; i--; ++pp) + if (*pp) + free(*pp); + free(pglob->gl_pathv); + pglob->gl_pathv = NULL; + } +} + +static DIR * +g_opendir(Char *str, glob_t *pglob) +{ + char buf[MAXPATHLEN]; + + if (!*str) + strcpy(buf, "."); + else { + if (g_Ctoc(str, buf, sizeof(buf))) + return (NULL); + } + + if (pglob->gl_flags & GLOB_ALTDIRFUNC) + return((*pglob->gl_opendir)(buf)); + + return(opendir(buf)); +} + +static int +g_lstat(Char *fn, struct stat *sb, glob_t *pglob) +{ + char buf[MAXPATHLEN]; + + if (g_Ctoc(fn, buf, sizeof(buf))) { + errno = ENAMETOOLONG; + return (-1); + } + if (pglob->gl_flags & GLOB_ALTDIRFUNC) + return((*pglob->gl_lstat)(buf, sb)); + return(lstat(buf, sb)); +} + +static int +g_stat(Char *fn, struct stat *sb, glob_t *pglob) +{ + char buf[MAXPATHLEN]; + + if (g_Ctoc(fn, buf, sizeof(buf))) { + errno = ENAMETOOLONG; + return (-1); + } + if (pglob->gl_flags & GLOB_ALTDIRFUNC) + return((*pglob->gl_stat)(buf, sb)); + return(stat(buf, sb)); +} + +static const Char * +g_strchr(const Char *str, wchar_t ch) +{ + + do { + if (*str == ch) + return (str); + } while (*str++); + return (NULL); +} + +static int +g_Ctoc(const Char *str, char *buf, size_t len) +{ + mbstate_t mbs; + size_t clen; + + memset(&mbs, 0, sizeof(mbs)); + while (len >= MB_CUR_MAX) { + clen = wcrtomb(buf, *str, &mbs); + if (clen == (size_t)-1) + return (1); + if (*str == L'\0') + return (0); + str++; + buf += clen; + len -= clen; + } + return (1); +} + +#ifdef DEBUG +static void +qprintf(const char *str, Char *s) +{ + Char *p; + + (void)printf("%s:\n", str); + for (p = s; *p; p++) + (void)printf("%c", CHAR(*p)); + (void)printf("\n"); + for (p = s; *p; p++) + (void)printf("%c", *p & M_PROTECT ? '"' : ' '); + (void)printf("\n"); + for (p = s; *p; p++) + (void)printf("%c", ismeta(*p) ? '_' : ' '); + (void)printf("\n"); +} +#endif
diff --git a/logrotate/glob.h b/logrotate/glob.h new file mode 100644 index 0000000..cf0c88e --- /dev/null +++ b/logrotate/glob.h
@@ -0,0 +1,100 @@ +/* + * Copyright (c) 1989, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Guido van Rossum. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)glob.h 8.1 (Berkeley) 6/2/93 + * $FreeBSD$ + */ + +#ifndef _GLOB_H_ +#define _GLOB_H_ + +#include <sys/cdefs.h> + +#ifndef _SIZE_T_DECLARED +typedef __size_t size_t; +#define _SIZE_T_DECLARED +#endif + +struct stat; +typedef struct { + size_t gl_pathc; /* Count of total paths so far. */ + size_t gl_matchc; /* Count of paths matching pattern. */ + size_t gl_offs; /* Reserved at beginning of gl_pathv. */ + int gl_flags; /* Copy of flags parameter to glob. */ + char **gl_pathv; /* List of paths matching pattern. */ + /* Copy of errfunc parameter to glob. */ + int (*gl_errfunc)(const char *, int); + + /* + * Alternate filesystem access methods for glob; replacement + * versions of closedir(3), readdir(3), opendir(3), stat(2) + * and lstat(2). + */ + void (*gl_closedir)(void *); + struct dirent *(*gl_readdir)(void *); + void *(*gl_opendir)(const char *); + int (*gl_lstat)(const char *, struct stat *); + int (*gl_stat)(const char *, struct stat *); +} glob_t; + +/* Believed to have been introduced in 1003.2-1992 */ +#define GLOB_APPEND 0x0001 /* Append to output from previous call. */ +#define GLOB_DOOFFS 0x0002 /* Use gl_offs. */ +#define GLOB_ERR 0x0004 /* Return on error. */ +#define GLOB_MARK 0x0008 /* Append / to matching directories. */ +#define GLOB_NOCHECK 0x0010 /* Return pattern itself if nothing matches. */ +#define GLOB_NOSORT 0x0020 /* Don't sort. */ +#define GLOB_NOESCAPE 0x2000 /* Disable backslash escaping. */ + +/* Error values returned by glob(3) */ +#define GLOB_NOSPACE (-1) /* Malloc call failed. */ +#define GLOB_ABORTED (-2) /* Unignored error. */ +#define GLOB_NOMATCH (-3) /* No match and GLOB_NOCHECK was not set. */ +#define GLOB_NOSYS (-4) /* Obsolete: source comptability only. */ + +#define GLOB_ALTDIRFUNC 0x0040 /* Use alternately specified directory funcs. */ +#define GLOB_BRACE 0x0080 /* Expand braces ala csh. */ +#define GLOB_MAGCHAR 0x0100 /* Pattern had globbing characters. */ +#define GLOB_NOMAGIC 0x0200 /* GLOB_NOCHECK without magic chars (csh). */ +#define GLOB_QUOTE 0x0400 /* Quote special chars with \. */ +#define GLOB_TILDE 0x0800 /* Expand tilde names from the passwd file. */ +#define GLOB_LIMIT 0x1000 /* limit number of returned paths */ + +/* source compatibility, these are the old names */ +#define GLOB_MAXPATH GLOB_LIMIT +#define GLOB_ABEND GLOB_ABORTED + +__BEGIN_DECLS +int glob(const char *, int, int (*)(const char *, int), glob_t *); +void globfree(glob_t *); +__END_DECLS + +#endif /* !_GLOB_H_ */
diff --git a/logrotate/log.c b/logrotate/log.c new file mode 100644 index 0000000..b3df49e --- /dev/null +++ b/logrotate/log.c
@@ -0,0 +1,134 @@ +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <time.h> +#include <sys/time.h> +#include <unistd.h> +#ifdef HAVE_VSYSLOG +#include <syslog.h> +#endif + +#include "log.h" +#include <android/log.h> +#define LOG_TAG "logrotate" + +int logLevel = MESS_DEBUG; +static FILE *errorFile = NULL; +static FILE *messageFile = NULL; +static int _logToSyslog = 0; +int flags = 0; + +void logSetLevel(int level) +{ + logLevel = level; +} + +void logSetErrorFile(FILE * f) +{ + errorFile = f; +} + +void logSetMessageFile(FILE * f) +{ + messageFile = f; +} + +void logToSyslog(int enable) { + _logToSyslog = enable; + +#ifdef HAVE_VSYSLOG + if (_logToSyslog) { + openlog("logrotate", 0, LOG_USER); + } + else { + closelog(); + } +#endif +} + +void logSetFlags(int newFlags) +{ + flags |= newFlags; +} + +void logClearFlags(int newFlags) +{ + flags &= ~newFlags; +} + +static void log_once(FILE *where, int level, char *format, va_list args) +{ + int showTime = 0; + + switch (level) { + case MESS_DEBUG: + showTime = 1; + break; + case MESS_NORMAL: + case MESS_VERBOSE: + break; + default: + if (flags & LOG_TIMES) + fprintf(where, "%ld: ", (long) time(NULL)); + fprintf(where, "error: "); + break; + } + + if (showTime && (flags & LOG_TIMES)) { + fprintf(where, "%ld:", (long) time(NULL)); + } + + vfprintf(where, format, args); + fflush(where); +} + +void message(int level, char *format, ...) +{ + va_list args; + + if (level >= logLevel) { + va_start(args, format); + __android_log_vprint(level, LOG_TAG, format, args); + log_once(stderr, level, format, args); + va_end(args); + } + + if (messageFile != NULL) { + va_start(args, format); + log_once(messageFile, level, format, args); + va_end(args); + } + +#ifdef HAVE_VSYSLOG + if (_logToSyslog) { + int priority = LOG_USER; + + switch(level) { + case MESS_REALDEBUG: + priority |= LOG_DEBUG; + break; + case MESS_DEBUG: + case MESS_VERBOSE: + case MESS_NORMAL: + priority |= LOG_INFO; + break; + case MESS_ERROR: + priority |= LOG_ERR; + break; + case MESS_FATAL: + priority |= LOG_CRIT; + break; + default: + priority |= LOG_INFO; + break; + }; + + va_start(args, format); + vsyslog(priority, format, args); + va_end(args); + } +#endif + + if (level == MESS_FATAL) + exit(1); +}
diff --git a/logrotate/log.h b/logrotate/log.h new file mode 100644 index 0000000..4a9f161 --- /dev/null +++ b/logrotate/log.h
@@ -0,0 +1,29 @@ +#ifndef H_LOG +#define H_LOG + +#include <stdio.h> + +#define MESS_REALDEBUG 1 +#define MESS_DEBUG 2 +#define MESS_VERBOSE 3 +#define MESS_NORMAL 4 //ANDROID_LOG_INFO +#define MESS_WARN 5 //ANDROID_LOG_WARN +#define MESS_ERROR 6 //ANDROID_LOG_ERROR +#define MESS_FATAL 7 //ANDROID_LOG_FATAL + +#define LOG_TIMES (1 << 0) + +void message(int level, char *format, ...) +#ifdef __GNUC__ + __attribute__ ((format(printf, 2, 3))); +#else +; +#endif +void logSetErrorFile(FILE * f); +void logSetMessageFile(FILE * f); +void logToSyslog(int enable); +void logSetFlags(int flags); +void logClearFlags(int flags); +void logSetLevel(int level); + +#endif
diff --git a/logrotate/log_sender b/logrotate/log_sender new file mode 100755 index 0000000..7455b48 --- /dev/null +++ b/logrotate/log_sender
@@ -0,0 +1,198 @@ +#!/system/bin/sh + +# Copyright 2016 Nest Labs, Inc. All rights reserved. + +# Base directory that contains any log reporter state files. +LOG_STATE_DIR="/data/misc/logrotate" +LOG_DIR="/data/misc/logd/" + +# Max upload/download rate +LIMIT_RATE="200K" + +# Log sender lock in case the sender is already running. +LOG_SENDER_LOCK="${LOG_STATE_DIR}/lock/log_sender" +# File descriptor to reference lock file, single digit only in Android Shell(mksh) +LOG_SENDER_LOCK_FD=9 + +# Path to a CA certificate file for log server +CA_CERTIFICATES_FILE_PATH="/system/etc/dropcam_calist.pem" +SSL_CERT="/data/misc/certs/device_cert.pem" +SSL_CERT_TYPE="PEM" +SSL_KEY="/data/misc/certs/device_cert.key" +SSL_KEY_TYPE="PEM" + +# File whose existence implies we're running and not to start again. +RUN_FILE="${LOG_STATE_DIR}/run/log_sender.pid" + +# The tag for all logging we emit. +TAG="$(basename $0)[$$]" + +# Directory to store timestamp files indicating the uploads in the past 24 +# hours. +TIMESTAMPS_DIR="${LOG_STATE_DIR}/log_sender" + +# Temp directory for this process. +TMP_DIR="" + +lecho() { + log -t "${TAG}" "$@" +} + +lwarn() { + lecho -psyslog.warn "$@" +} + +die () { + lecho $@ + lecho "Exit" + exit 1 +} + +cleanup() { + if [ -n "${TMP_DIR}" ]; then + rm -rf "${TMP_DIR}" + fi + rm -f "${RUN_FILE}" + # clean all log_sender files in case things are going wrong + rm -rf /data/misc/logrotate/tmp/log_sender* +} + +# Returns true if uploading is enabled +is_upload_enabled() { + [ "$(get_key_value "log.upload_enabled")" -eq 1 ] && return 0 + return 1 +} + +get_key_value() { + local key="$1" value + + value=`getprop persist.nl.${key}` + + echo "${value:-undefined}" +} + +send_log() { + local log_path="$1" + local url="$(get_key_value "log.server")" + local send_size="$(stat -c "%s" "${log_path}" 2>/dev/null)" + local mac=`sysenv get hwaddr0` + # Cloud does not parse ":" in MAC address, so remove all of them + mac=${mac//":"/""} + + # If log_reporter.server is not set return with an error. + if [ -z "${url}" ]; then + lecho "Configuration error: log server not set." + return 1 + fi + + if [[ -z "${mac}" || ${#mac} -ne 12 ]]; then + lecho "Configuration error: mac address is invalid." + return 1 + fi + + lecho "Sending log:" + lecho " MAC: ${mac}" + lecho " File: ${log_path}" + lecho " URL: ${url}" + lecho " Size: ${send_size}" + + local curl_stderr="${TMP_DIR}/curl_stderr" + local curl_report="${TMP_DIR}/report" + + set +e + curl -vvv "${url}" \ + --cacert "${CA_CERTIFICATES_FILE_PATH}" \ + --cert "${SSL_CERT}" \ + --cert-type "${SSL_CERT_TYPE}" \ + --key "${SSL_KEY}" \ + --key-type "${SSL_KEY_TYPE}" \ + --limit-rate "${LIMIT_RATE}" \ + -F "mac=${mac}" \ + -F "file=@${log_path};type=application/octet-stream" \ + -o "${curl_report}" \ + 2>"${curl_stderr}" + curl_result=$? + set -e + + if [ -f ${curl_report} ]; then + lecho "Message from server: " \ + "$(cat "${curl_report}")" + fi + + if [ ${curl_result} -eq 0 ]; then + local timestamp="$(date +%s)" + lecho "Log ${log_path} has been sent at ${timestamp}" + else + lecho "Log sending failed with exit code ${curl_result}: " \ + "$(cat "${curl_stderr}")" + fi + + rm -f "${curl_report}" + rm -f "${curl_stderr}" + + return ${curl_result} +} + +# *.meta files always end with done=1 so we can tell if they are complete. +# Remove the given report path. +remove_report() { + rm -f -- "${1}" +} + +# Send all logs from the given directory. +send_logs() { + local dir="$1" + lecho "Sending logs for ${dir}" + + if [ ! -d "${dir}" ]; then + die "Can't find directory ${dir}" + fi + + # start from old + for log_path in $(ls -1tr "${dir}"/messages.*.gz 2>/dev/null); do + lecho "Considering log ${log_path}." + + # Try to upload. + if ! send_log "${log_path}"; then + lecho "Problem sending ${log_path}, not removing." + continue + fi + + # Send was successful, now remove. + lecho "Successfully sent log ${log_path} and removing." + remove_report "${log_path}" + done +} + +main() { + + lecho "Starting log uploading..." + + if ! is_upload_enabled; + then + lecho "Log uploading is disabled. Exit." + exit 0; + fi + + # We don't perform checks on this because we have a master lock with the + # LOG_SENDER_LOCK file. This pid file is for the system to keep track + # that we're still running. + echo $$ > "${RUN_FILE}" + + TMP_DIR="$(mktemp -d "${LOG_STATE_DIR}/tmp/log_sender.XXXXXX")" + if [ $? -ne 0 ]; then + die "Failed to create TMP_DIR: ${LOG_STATE_DIR}/tmp/log_sender.XXXXX" + fi + + # Send system-wide logs + send_logs "${LOG_DIR}" +} + +trap cleanup EXIT INT TERM + +mkdir -p $(dirname ${LOG_SENDER_LOCK}) +( + # -x:Exclusive lock, -n:Non-blocking + flock -xn 9 || die "Failed to acquire lock!" + main "$@" +) 9>$LOG_SENDER_LOCK
diff --git a/logrotate/logrotate.8 b/logrotate/logrotate.8 new file mode 100644 index 0000000..f6e6bc6 --- /dev/null +++ b/logrotate/logrotate.8
@@ -0,0 +1,594 @@ +.TH LOGROTATE 8 "Wed Nov 5 2002" "Linux" "System Administrator's Manual" +.SH NAME +logrotate \(hy rotates, compresses, and mails system logs +.SH SYNOPSIS +\fBlogrotate\fR [\fB\-dv\fR] [\fB\-f\fR|\fB\-\-force\fR] +[\fB\-s\fR|\fB\-\-state \fIfile\fR] \fIconfig_file\fR .. +.SH DESCRIPTION +\fBlogrotate\fR is designed to ease administration of systems that generate +large numbers of log files. It allows automatic rotation, compression, +removal, and mailing of log files. Each log file may be handled daily, +weekly, monthly, or when it grows too large. +.P +Normally, \fBlogrotate\fR is run as a daily cron job. It will not modify +a log more than once in one day unless the criterion for that log is +based on the log's size and \fBlogrotate\fR is being run more than once +each day, or unless the \fB\-f\fR or \fB\-\-force\fR option is used. +.P +Any number of config files may be given on the command line. Later config +files may override the options given in earlier files, so the order +in which the \fBlogrotate\fR config files are listed is important. +Normally, a single config file which includes any other config files +which are needed should be used. See below for more information on how +to use the \fBinclude\fR directive to accomplish this. If a directory +is given on the command line, every file in that directory is used as +a config file. +.P +If no command line arguments are given, \fBlogrotate\fR will print +version and copyright information, along with a short usage summary. If +any errors occur while rotating logs, \fBlogrotate\fR will exit with +non-zero status. + +.SH OPTIONS +.TP +\fB\-?\fR, \fB\-\-help\fR +Prints help message. + +.TP +\fB\-d\fR, \fB\-\-debug\fR +Turns on debug mode and implies \fB-v\fR. In debug mode, no changes will +be made to the logs or to the \fBlogrotate\fR state file. + +.TP +\fB\-f\fR, \fB\-\-force\fR +Tells \fBlogrotate\fR to force the rotation, even if it doesn't think +this is necessary. Sometimes this is useful after adding new entries to +a \fBlogrotate\fR config file, or if old log files have been removed +by hand, as the new files will be created, and logging will continue +correctly. + +.TP +\fB\-l <log_file>\fR +Tells \fBlogrotate\fR to log verbose output into the log_file. The verbose +output logged to that file is the same as when running \fBlogrotate\fR with +\fB-v\fR switch. The log file is overwritten on every logrotate execution. + +.TP +\fB\-m\fR, \fB\-\-mail <command>\fR +Tells \fBlogrotate\fR which command to use when mailing logs. This +command should accept two arguments: 1) the subject of the message, and +2) the recipient. The command must then read a message on standard input +and mail it to the recipient. The default mail command is \fB/bin/mail +-s\fR. + +.TP +\fB\-s\fR, \fB\-\-state <statefile>\fR +Tells \fBlogrotate\fR to use an alternate state file. This is useful +if logrotate is being run as a different user for various sets of +log files. The default state file is \fI/var/lib/logrotate.status\fR. + +.TP +\fB\-\-usage\fR +Prints a short usage message. + +.TP ++\fB\-v\fR, \fB\-\-verbose\fR +Turns on verbose mode, ie. display messages during rotation. + +.SH CONFIGURATION FILE + +\fBlogrotate\fR reads everything about the log files it should be handling +from the series of configuration files specified on the command line. Each +configuration file can set global options (local definitions override +global ones, and later definitions override earlier ones) and specify +logfiles to rotate. A simple configuration file looks like this: + +.nf +.ta +8n +# sample logrotate configuration file +compress + +/var/log/messages { + rotate 5 + weekly + postrotate + /usr/bin/killall \-HUP syslogd + endscript +} + +"/var/log/httpd/access.log" /var/log/httpd/error.log { + rotate 5 + mail www@my.org + size 100k + sharedscripts + postrotate + /usr/bin/killall \-HUP httpd + endscript +} + +/var/log/news/* { + monthly + rotate 2 + olddir /var/log/news/old + missingok + postrotate + kill \-HUP `cat /var/run/inn.pid` + endscript + nocompress +} + +~/log/*.log {} + +.fi + +.PP +The first few lines set global options; in the example, logs are +compressed after they are rotated. Note that comments may appear +anywhere in the config file as long as the first non-whitespace +character on the line is a \fB#\fR. + +Values are separated from directives by whitespace and/or an optional =. +Numbers must be specified in a format understood by \fBstrtoul(3)\fR. + +The next section of the config file defines how to handle the log file +\fI/var/log/messages\fR. The log will go through five weekly rotations before +being removed. After the log file has been rotated (but before the old +version of the log has been compressed), the command +\fI/sbin/killall \-HUP syslogd\fR will be executed. + +The next section defines the parameters for both +\fI/var/log/httpd/access.log\fR and \fI/var/log/httpd/error.log\fR. +Each is rotated whenever it grows over 100k in size, and the old logs +files are mailed (uncompressed) to www@my.org after going through 5 +rotations, rather than being removed. The \fBsharedscripts\fR means that +the \fBpostrotate\fR script will only be run once (after the old logs have +been compressed), not once for each log which is rotated. +Note that log file names may be enclosed in +quotes (and that quotes are required if the name contains spaces). +Normal shell quoting rules apply, with \fB'\fR, \fB"\fR, and \fB\\\fR +characters supported. + +The next section defines the parameters for all of the files in +\fI/var/log/news\fR. Each file is rotated on a monthly basis. This is +considered a single rotation directive and if errors occur for more than +one file, the log files are not compressed. + +The last section uses tilde expansion to rotate log files in the home +directory of the current user. This is only available, if your glob +library supports tilde expansion. GNU glob does support this. + +Please use wildcards with caution. If you specify *, \fBlogrotate\fR will +rotate all files, including previously rotated ones. A way around this +is to use the \fBolddir\fR directive or a more exact wildcard (such as *.log). + +Here is more information on the directives which may be included in +a \fBlogrotate\fR configuration file: + +.TP +\fBcompress\fR +Old versions of log files are compressed with \fBgzip\fR(1) by default. See also +\fBnocompress\fR. + +.TP +\fBcompresscmd\fR +Specifies which command to use to compress log files. The default is +\fBgzip\fR(1). See also \fBcompress\fR. + +.TP +\fBuncompresscmd\fR +Specifies which command to use to uncompress log files. The default is +\fBgunzip\fR(1). + +.TP +\fBcompressext\fR +Specifies which extension to use on compressed logfiles, if compression +is enabled. The default follows that of the configured compression +command. + +.TP +\fBcompressoptions\fR +Command line options may be passed to the compression program, if one is +in use. The default, for \fBgzip\fR(1), is "\-6" (biased towards high +compression at the expense of speed). +If you use a different compression command, you may need to change the +\fBcompressoptions\fR to match. + + +.TP +\fBcopy\fR +Make a copy of the log file, but don't change the original at all. +This option can be used, for instance, to make a snapshot of the current +log file, or when some other utility needs to truncate or parse the file. +When this option is used, the \fBcreate\fR option will have no effect, +as the old log file stays in place. + +.TP +\fBcopytruncate\fR +Truncate the original log file to zero size in place after creating a copy, +instead of moving the old log file and optionally creating a new one. +It can be used when some program cannot be told to close its logfile +and thus might continue writing (appending) to the previous log file forever. +Note that there is a very small time slice between copying the file and +truncating it, so some logging data might be lost. +When this option is used, the \fBcreate\fR option will have no effect, +as the old log file stays in place. + +.TP +\fBcreate \fImode\fR \fIowner\fR \fIgroup\fR, \fBcreate \fIowner\fR \fIgroup\fR +Immediately after rotation (before the \fBpostrotate\fR script is run) +the log file is created (with the same name as the log file just rotated). +\fImode\fR specifies the mode for the log file in octal (the same +as \fBchmod\fR(2)), \fIowner\fR specifies the user name who will own the +log file, and \fIgroup\fR specifies the group the log file will belong +to. Any of the log file attributes may be omitted, in which case those +attributes for the new file will use the same values as the original log +file for the omitted attributes. This option can be disabled using the +\fBnocreate\fR option. + +.TP +\fBcreateolddir \fImode\fR \fIowner\fR \fIgroup\fR +If the directory specified by \fBolddir\fR directive does not exist, it is +created. \fImode\fR specifies the mode for the \fBolddir\fR directory +in octal (the same as \fBchmod\fR(2)), \fIowner\fR specifies the user name +who will own the \fBolddir\fR directory, and \fIgroup\fR specifies the group +the \fBolddir\fR directory will belong to. This option can be disabled using the +\fBnocreateolddir\fR option. + + +.TP +\fBdaily\fR +Log files are rotated every day. + +.TP +\fBdateext\fR +Archive old versions of log files adding a date extension like YYYYMMDD +instead of simply adding a number. The extension may be configured using +the \fBdateformat\fR and \fBdateyesterday\fR options. + +.TP +\fBdateformat\fR \fIformat_string\fR +Specify the extension for \fBdateext\fR using the notation similar to +\fBstrftime\fR(3) function. Only %Y %m %d %H %M %S %V and %s specifiers are +allowed. +The default value is \-%Y%m%d except hourly, which uses \-%Y%m%d%H as default +value. Note that also the character separating log name from the extension is +part of the dateformat string. The system clock must be set past Sep 9th 2001 +for %s to work correctly. +Note that the datestamps generated by this format must be lexically sortable +(i.e., first the year, then the month then the day. e.g., 2001/12/01 is ok, +but 01/12/2001 is not, since 01/11/2002 would sort lower while it is later). +This is because when using the \fBrotate\fR option, logrotate sorts all +rotated filenames to find out which logfiles are older and should be removed. + +.TP +\fBdateyesterday\fR +Use yesterday's instead of today's date to create the \fBdateext\fR +extension, so that the rotated log file has a date in its name that is +the same as the timestamps within it. + +.TP +\fBdelaycompress\fR +Postpone compression of the previous log file to the next rotation cycle. +This only has effect when used in combination with \fBcompress\fR. +It can be used when some program cannot be told to close its logfile +and thus might continue writing to the previous log file for some time. + +.TP +\fBextension \fIext\fR +Log files with \fIext\fR extension can keep it after the rotation. +If compression is used, the compression extension (normally \fI.gz\fR) +appears after \fIext\fR. For example you have a logfile named mylog.foo +and want to rotate it to mylog.1.foo.gz instead of mylog.foo.1.gz. + +.TP +\fBhourly\fR +Log files are rotated every hour. Note that usually \fIlogrotate\fR is +configured to be run by cron daily. You have to change this configuration +and run \fIlogrotate\fR hourly to be able to really rotate logs hourly. + +.TP +\fBaddextension \fIext\fR +Log files are given the final extension \fIext\fR after rotation. If +the original file already ends with \fIext\fR, the extension is not +duplicated, but merely moved to the end, i.e. both \fBfilename\fR and +\fBfilename\fIext\fR would get rotated to filename.1\fIext\fR. If +compression is used, the compression extension (normally \fB.gz\fR) +appears after \fIext\fR. + +.TP +\fBifempty\fR +Rotate the log file even if it is empty, overriding the \fBnotifempty\fR +option (\fBifempty\fR is the default). + +.TP +\fBinclude \fIfile_or_directory\fR +Reads the file given as an argument as if it was included inline +where the \fBinclude\fR directive appears. If a directory is given, +most of the files in that directory are read in alphabetic order +before processing of the including file continues. The only files +which are ignored are files which are not regular files (such as +directories and named pipes) and files whose names end with one of +the taboo extensions, as specified by the \fBtabooext\fR directive. + +.TP +\fBmail \fIaddress\fR +When a log is rotated out of existence, it is mailed to \fIaddress\fR. If +no mail should be generated by a particular log, the \fBnomail\fR directive +may be used. + +.TP +\fBmailfirst\fR +When using the \fBmail\fR command, mail the just-rotated file, +instead of the about-to-expire file. + +.TP +\fBmaillast\fR +When using the \fBmail\fR command, mail the about-to-expire file, +instead of the just-rotated file (this is the default). + +.TP +\fBminage\fR \fIcount\fR +Do not rotate logs which are less than <count> days old. + +.TP +\fBmaxage\fR \fIcount\fR +Remove rotated logs older than <count> days. The age is only checked +if the logfile is to be rotated. The files are mailed to the +configured address if \fBmaillast\fR and \fBmail\fR are configured. + +.TP +\fBmaxsize\fR \fIsize\fR +Log files are rotated when they grow bigger than \fIsize\fR bytes even +before the additionally specified time interval (\fBdaily\fR, \fBweekly\fR, +\fBmonthly\fR, or \fByearly\fR). The related \fBsize\fR option is similar +except that it is mutually exclusive with the time interval options, and it +causes log files to be rotated without regard for the last rotation time. +When \fBmaxsize\fR is used, both the size and timestamp of a log file are +considered. + +.TP +\fBminsize\fR \fIsize\fR +Log files are rotated when they grow bigger than \fIsize\fR bytes, but not +before the additionally specified time interval (\fBdaily\fR, \fBweekly\fR, +\fBmonthly\fR, or \fByearly\fR). The related \fBsize\fR option is similar +except that it is mutually exclusive with the time interval options, and it +causes log files to be rotated without regard for the last rotation time. +When \fBminsize\fR is used, both the size and timestamp of a log file are +considered. + +.TP +\fBmissingok\fR +If the log file is missing, go on to the next one without issuing an error +message. See also \fBnomissingok\fR. + +.TP +\fBmonthly\fR +Log files are rotated the first time \fBlogrotate\fR is run in a month +(this is normally on the first day of the month). + +.TP +\fBnocompress\fR +Old versions of log files are not compressed. See also \fBcompress\fR. + +.TP +\fBnocopy\fR +Do not copy the original log file and leave it in place. +(this overrides the \fBcopy\fR option). + +.TP +\fBnocopytruncate\fR +Do not truncate the original log file in place after creating a copy +(this overrides the \fBcopytruncate\fR option). + +.TP +\fBnocreate\fR +New log files are not created (this overrides the \fBcreate\fR option). + +.TP +\fBnocreateolddir\fR +\fBolddir\fR directory is not created by logrotate when it does not exist. + +.TP +\fBnodelaycompress\fR +Do not postpone compression of the previous log file to the next rotation cycle +(this overrides the \fBdelaycompress\fR option). + +.TP +\fBnodateext\fR +Do not archive old versions of log files with date extension +(this overrides the \fBdateext\fR option). + +.TP +\fBnomail\fR +Do not mail old log files to any address. + +.TP +\fBnomissingok\fR +If a log file does not exist, issue an error. This is the default. + +.TP +\fBnoolddir\fR +Logs are rotated in the directory they normally reside in (this +overrides the \fBolddir\fR option). + +.TP +\fBnosharedscripts\fR +Run \fBprerotate\fR and \fBpostrotate\fR scripts for every log file which +is rotated (this is the default, and overrides the \fBsharedscripts\fR +option). The absolute path to the log file is passed as first argument +to the script. If the scripts exit with error, the remaining actions will +not be executed for the affected log only. + +.TP +\fBnoshred\fR +Do not use \fBshred\fR when deleting old log files. See also \fBshred\fR. + +.TP +\fBnotifempty\fR +Do not rotate the log if it is empty (this overrides the \fBifempty\fR option). + +.TP +\fBolddir \fIdirectory\fR +Logs are moved into \fIdirectory\fR for rotation. The \fIdirectory\fR must be +on the same physical device as the log file being rotated, unless \fBcopy\fR, +\fBcopytruncate\fR or \fBrenamecopy\fR option is used. The \fIdirectory\fR +is assumed to be relative to the directory holding the log file +unless an absolute path name is specified. When this option is used all +old versions of the log end up in \fIdirectory\fR. This option may be +overridden by the \fBnoolddir\fR option. + +.TP +\fBpostrotate\fR/\fBendscript\fR +The lines between \fBpostrotate\fR and \fBendscript\fR (both of which +must appear on lines by themselves) are executed (using \fB/bin/sh\fR) +after the log file is rotated. These directives may only appear inside +a log file definition. Normally, the absolute path to the log file is +passed as first argument to the script. If \fBsharedscripts\fR is specified, +whole pattern is passed to the script. +See also \fBprerotate\fR. See \fBsharedscripts\fR and \fBnosharedscripts\fR +for error handling. + +.TP +\fBprerotate\fR/\fBendscript\fR +The lines between \fBprerotate\fR and \fBendscript\fR (both of which +must appear on lines by themselves) are executed (using \fB/bin/sh\fR) before +the log file is rotated and only if the log will actually be rotated. These +directives may only appear inside a log file definition. Normally, +the absolute path to the log file is passed as first argument to the script. +If \fBsharedscripts\fR is specified, whole pattern is passed to the script. +See also \fBpostrotate\fR. +See \fBsharedscripts\fR and \fBnosharedscripts\fR for error handling. + +.TP +\fBfirstaction\fR/\fBendscript\fR +The lines between \fBfirstaction\fR and \fBendscript\fR (both of which +must appear on lines by themselves) are executed (using \fB/bin/sh\fR) once +before all log files that match the wildcarded pattern are rotated, before +prerotate script is run and only if at least one log will actually be rotated. +These directives may only appear inside a log file definition. Whole pattern is +passed to the script as first argument. If the script exits with error, +no further processing is done. See also \fBlastaction\fR. + +.TP +\fBlastaction\fR/\fBendscript\fR +The lines between \fBlastaction\fR and \fBendscript\fR (both of which +must appear on lines by themselves) are executed (using \fB/bin/sh\fR) once +after all log files that match the wildcarded pattern are rotated, after +postrotate script is run and only if at least one log is rotated. These +directives may only appear inside a log file definition. Whole pattern is +passed to the script as first argument. If the script exits +with error, just an error message is shown (as this is the last +action). See also \fBfirstaction\fR. + +.TP +\fBpreremove\fR/\fBendscript\fR +The lines between \fBpreremove\fR and \fBendscript\fR (both of which must +appear on lines by themselves) are executed (using \fB/bin/sh\fR) once just +before removal of a log file. The logrotate will pass +the name of file which is soon to be removed. See also \fBfirstaction\fR. + +.TP +\fBrotate \fIcount\fR +Log files are rotated \fIcount\fR times before being removed or mailed to the +address specified in a \fBmail\fR directive. If \fIcount\fR is 0, old versions +are removed rather than rotated. Default is 0. + +.TP +\fBrenamecopy\fR +Log file is renamed to temporary filename in the same directory by adding +".tmp" extension to it. After that, \fBpostrotate\fR script is run +and log file is copied from temporary filename to final filename. This allows +storing rotated log files on the different devices using \fBolddir\fR +directive. In the end, temporary filename is removed. + +.TP +\fBsize \fIsize\fR +Log files are rotated only if they grow bigger than \fIsize\fR bytes. If +\fIsize\fR is followed by \fIk\fR, the size is assumed to be in kilobytes. +If the \fIM\fR is used, the size is in megabytes, and if \fIG\fR is used, the +size is in gigabytes. So \fIsize 100\fR, \fIsize 100k\fR, \fIsize 100M\fR and +\fIsize 100G\fR are all valid. + +.TP +\fBsharedscripts\fR +Normally, \fBprerotate\fR and \fBpostrotate\fR scripts are run for each +log which is rotated and the absolute path to the log file is passed as first +argument to the script. That means a single script may be run multiple +times for log file entries which match multiple files (such as the +\fI/var/log/news/*\fR example). If \fBsharedscripts\fR is specified, the scripts +are only run once, no matter how many logs match the wildcarded pattern, +and whole pattern is passed to them. +However, if none of the logs in the pattern require rotating, the scripts +will not be run at all. If the scripts exit with error, the remaining +actions will not be executed for any logs. This option overrides the +\fBnosharedscripts\fR option and implies \fBcreate\fR option. + +.TP +\fBshred\fR +Delete log files using \fBshred\fR \-u instead of unlink(). This should +ensure that logs are not readable after their scheduled deletion; this is +off by default. See also \fBnoshred\fR. + +.TP +\fBshredcycles\fR \fIcount\fR +Asks GNU \fBshred\fR(1) to overwrite log files \fBcount\fR times before +deletion. Without this option, \fBshred\fR's default will be used. + +.TP +\fBstart \fIcount\fR +This is the number to use as the base for rotation. For example, if +you specify 0, the logs will be created with a .0 extension as they are +rotated from the original log files. If you specify 9, log files will +be created with a .9, skipping 0-8. Files will still be rotated the +number of times specified with the \fBrotate\fR directive. + +.TP +\fBsu \fIuser\fR \fIgroup\fR +Rotate log files set under this user and group instead of using default +user/group (usually root). \fIuser\fR specifies the user name used for +rotation and \fIgroup\fR specifies the group used for rotation. If the +user/group you specify here does not have sufficient privilege to make +files with the ownership you've specified in a \fIcreate\fR instruction, +it will cause an error. + +.TP +\fBtabooext\fR [+] \fIlist\fR +The current taboo extension list is changed (see the \fBinclude\fR directive +for information on the taboo extensions). If a + precedes the list of +extensions, the current taboo extension list is augmented, otherwise it +is replaced. At startup, the taboo extension list +contains .rpmsave, .rpmorig, ~, .disabled, .dpkg\-old, .dpkg\-dist, .dpkg\-new, .cfsaved, .ucf\-old, .ucf\-dist, .ucf\-new, .rpmnew, .swp, .cfsaved, .rhn\-cfg\-tmp\-* + +.TP +\fBweekly\fR +Log files are rotated if the current weekday is less than the weekday +of the last rotation or if more than a week has passed since the last +rotation. This is normally the same as rotating logs on the first day +of the week, but it works better if \fIlogrotate\fR is not run every +night. + +.TP +\fByearly\fR +Log files are rotated if the current year is not the same as the last rotation. + +.SH FILES +.PD 0 +.TP 27 +\fI/var/lib/logrotate.status\fR +Default state file. +.TP 27 +\fI/etc/logrotate.conf\fR +Configuration options. + +.SH SEE ALSO +.BR gzip (1) + +<https://github.com/logrotate/logrotate> + +.SH AUTHORS +.nf +Erik Troan, Preston Brown, Jan Kaluza. + +<https://github.com/logrotate/logrotate> + +.fi
diff --git a/logrotate/logrotate.c b/logrotate/logrotate.c new file mode 100644 index 0000000..674fac2 --- /dev/null +++ b/logrotate/logrotate.c
@@ -0,0 +1,2559 @@ +#include "queue.h" +/* alloca() is defined in stdlib.h in NetBSD */ +#ifndef __NetBSD__ +#include <alloca.h> +#endif +#include <limits.h> +#include <ctype.h> +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <popt.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <time.h> +#include <unistd.h> +#include <glob.h> +#include <locale.h> +#include <sys/types.h> +#include <utime.h> +#include <stdint.h> + +#if defined(SunOS) +#include <limits.h> +#endif + +#include "basenames.h" +#include "log.h" +#include "logrotate.h" + +#ifdef WITH_SELINUX +#include <selinux/selinux.h> +static security_context_t prev_context = NULL; +int selinux_enabled = 0; +int selinux_enforce = 0; +#else +void * prev_context = NULL; +#endif + +#ifdef WITH_ACL +#include "sys/acl.h" +#define acl_type acl_t +#define ACL_NOT_WELL_SUPPORTED(Err) \ + ((Err) == ENOTSUP || (Err) == ENOSYS || (Err) == EINVAL || (Err) == EBUSY) +#else +#define acl_type void * +#endif + +static acl_type prev_acl = NULL; + +#if !defined(GLOB_ABORTED) && defined(GLOB_ABEND) +#define GLOB_ABORTED GLOB_ABEND +#endif + +#ifdef PATH_MAX +#define STATEFILE_BUFFER_SIZE 2 * PATH_MAX + 16 +#else +#define STATEFILE_BUFFER_SIZE 4096 +#endif + +#ifdef __hpux +extern int asprintf(char **str, const char *fmt, ...); +#endif + +#if defined(HAVE_FORK) +#define FORK_OR_VFORK fork +#define DOEXIT exit +#elif defined(HAVE_VFORK) +#define FORK_OR_VFORK vfork +#define DOEXIT _exit +#else +#define FORK_OR_VFORK fork +#define DOEXIT exit +#endif + +// Number of seconds in a day +#define DAY_SECONDS 86400 + +struct logState { + char *fn; + struct tm lastRotated; /* only tm_hour, tm_mday, tm_mon, tm_year are good! */ + struct stat sb; + int doRotate; + int isUsed; /* True if there is real log file in system for this state. */ + LIST_ENTRY(logState) list; +}; + +struct logNames { + char *firstRotated; + char *disposeName; + char *finalName; + char *dirName; + char *baseName; +}; + +struct compData { + int prefix_len; + const char *dformat; +}; + +struct logStates { + LIST_HEAD(stateSet, logState) head; +} **states; + +unsigned int hashSize; +int numLogs = 0; +int debug = 0; +char *mailCommand = DEFAULT_MAIL_COMMAND; +time_t nowSecs = 0; +static uid_t save_euid; +static gid_t save_egid; + +static int shred_file(int fd, char *filename, struct logInfo *log); + +static int globerr(const char *pathname, int theerr) +{ + message(MESS_ERROR, "error accessing %s: %s\n", pathname, + strerror(theerr)); + + /* We want the glob operation to continue, so return 0 */ + return 1; +} + +#if defined(HAVE_STRPTIME) && defined(HAVE_QSORT) + +/* We could switch to qsort_r to get rid of this global variable, + * but qsort_r is not portable enough (Linux vs. *BSD vs ...)... */ +static struct compData _compData; + +static int compGlobResult(const void *result1, const void *result2) { + struct tm time; + time_t t1, t2; + const char *r1 = *(const char **)(result1); + const char *r2 = *(const char **)(result2); + + memset(&time, 0, sizeof(struct tm)); + strptime(r1 + _compData.prefix_len, _compData.dformat, &time); + t1 = mktime(&time); + + memset(&time, 0, sizeof(struct tm)); + strptime(r2 + _compData.prefix_len, _compData.dformat, &time); + t2 = mktime(&time); + + if (t1 < t2) return -1; + if (t1 > t2) return 1; + return 0; +} + +static void sortGlobResult(glob_t *result, int prefix_len, const char *dformat) { + if (!dformat || *dformat == '\0') { + return; + } + + _compData.prefix_len = prefix_len; + _compData.dformat = dformat; + qsort(result->gl_pathv, result->gl_pathc, sizeof(char *), compGlobResult); +} +#else +static void sortGlobResult(glob_t *result, int prefix_len, const char *dformat) { + /* TODO */ +} +#endif + +int switch_user(uid_t user, gid_t group) { + save_egid = getegid(); + save_euid = geteuid(); + if (save_euid == user && save_egid == group) + return 0; + message(MESS_DEBUG, "switching euid to %d and egid to %d\n", + user, group); + if (setegid(group) || seteuid(user)) { + message(MESS_ERROR, "error switching euid to %d and egid to %d: %s\n", + user, group, strerror(errno)); + return 1; + } + return 0; +} + +int switch_user_permanently(const struct logInfo *log) { + gid_t group = getegid(); + uid_t user = geteuid(); + if (!(log->flags & LOG_FLAG_SU)) { + return 0; + } + if (getuid() == user && getgid() == group) + return 0; + // switch to full root first + if (setgid(getgid()) || setuid(getuid())) { + message(MESS_ERROR, "error getting rid of euid != uid\n"); + return 1; + } + message(MESS_DEBUG, "switching uid to %d and gid to %d\n", + user, group); + if (setgid(group) || setuid(user)) { + message(MESS_ERROR, "error switching euid to %d and egid to %d: %s\n", + user, group, strerror(errno)); + return 1; + } + return 0; +} + +int switch_user_back() { + return switch_user(save_euid, save_egid); +} + +int switch_user_back_permanently() { + gid_t tmp_egid = save_egid; + uid_t tmp_euid = save_euid; + int ret = switch_user(save_euid, save_egid); + save_euid = tmp_euid; + save_egid = tmp_egid; + return ret; +} + +static void unescape(char *arg) +{ + char *p = arg; + char *next; + char escaped; + while ((next = strchr(p, '\\')) != NULL) { + + p = next; + + switch (p[1]) { + case 'n': + escaped = '\n'; + break; + case '\\': + escaped = '\\'; + break; + default: + ++p; + continue; + } + + /* Overwrite the backslash with the intended character, + * and shift everything down one */ + *p++ = escaped; + memmove(p, p+1, 1 + strlen(p+1)); + } +} + +#define HASH_SIZE_MIN 64 +static int allocateHash(unsigned int hs) +{ + int i; + + /* Enforce some reasonable minimum hash size */ + if (hs < HASH_SIZE_MIN) + hs = HASH_SIZE_MIN; + + message(MESS_DEBUG, "Allocating hash table for state file, size %d entries\n", + hs); + + states = calloc(hs, sizeof(struct logStates *)); + if (states == NULL) { + message(MESS_ERROR, "could not allocate memory for " + "hash table\n"); + return 1; + } + + for (i = 0; i < hs; i++) { + states[i] = malloc(sizeof(struct logState)); + if (states[i] == NULL) { + message(MESS_ERROR, "could not allocate memory for " + "hash element\n"); + return 1; + } + LIST_INIT(&(states[i]->head)); + } + + hashSize = hs; + + return 0; +} + +#define HASH_CONST 13 +static unsigned hashIndex(const char *fn) +{ + unsigned hash = 0; + + while (*fn) { + hash *= HASH_CONST; + hash += *fn++; + } + + return hash % hashSize; +} + +static int setSecCtx(int fdSrc, const char *src, void **pPrevCtx) +{ +#ifdef WITH_SELINUX + security_context_t srcCtx; + *pPrevCtx = NULL; + + if (!selinux_enabled) + /* pretend success */ + return 0; + + /* read security context of fdSrc */ + if (fgetfilecon_raw(fdSrc, &srcCtx) < 0) { + if (errno == ENOTSUP) + /* pretend success */ + return 0; + + message(MESS_ERROR, "getting file context %s: %s\n", src, + strerror(errno)); + return selinux_enforce; + } + + /* save default security context for restoreSecCtx() */ + if (getfscreatecon_raw((security_context_t *)pPrevCtx) < 0) { + message(MESS_ERROR, "getting default context: %s\n", strerror(errno)); + return selinux_enforce; + } + + /* set default security context to match fdSrc */ + if (setfscreatecon_raw(srcCtx) < 0) { + message(MESS_ERROR, "setting default context to %s: %s\n", srcCtx, + strerror(errno)); + freecon(srcCtx); + return selinux_enforce; + } + + message(MESS_DEBUG, "set default create context to %s\n", srcCtx); + freecon(srcCtx); +#else + (void) fdSrc; + (void) src; + (void) pPrevCtx; +#endif + return 0; +} + +static int setSecCtxByName(const char *src, void **pPrevCtx) +{ + int hasErrors = 0; +#ifdef WITH_SELINUX + int fd = open(src, O_RDONLY | O_NOFOLLOW); + if (fd < 0) { + message(MESS_ERROR, "error opening %s: %s\n", src, strerror(errno)); + return 1; + } + hasErrors = setSecCtx(fd, src, pPrevCtx); + close(fd); +#else + (void) src; + (void) pPrevCtx; +#endif + return hasErrors; +} + +static void restoreSecCtx(void **pPrevCtx) +{ +#ifdef WITH_SELINUX + const security_context_t prevCtx = (security_context_t) *pPrevCtx; + if (!prevCtx) + /* no security context saved for restoration */ + return; + + /* set default security context to the previously stored one */ + if (selinux_enabled && setfscreatecon_raw(prevCtx) < 0) + message(MESS_ERROR, "setting default context to %s: %s\n", prevCtx, + strerror(errno)); + + /* free the memory allocated to save the security context */ + freecon(prevCtx); + *pPrevCtx = NULL; +#else + (void) pPrevCtx; +#endif +} + +static struct logState *newState(const char *fn) +{ + struct tm now = *localtime(&nowSecs); + struct logState *new; + time_t lr_time; + + message(MESS_DEBUG, "Creating new state\n"); + + if ((new = malloc(sizeof(*new))) == NULL) + return NULL; + + if ((new->fn = strdup(fn)) == NULL) { + free(new); + return NULL; + } + + new->doRotate = 0; + new->isUsed = 0; + + memset(&new->lastRotated, 0, sizeof(new->lastRotated)); + new->lastRotated.tm_hour = now.tm_hour; + new->lastRotated.tm_mday = now.tm_mday; + new->lastRotated.tm_mon = now.tm_mon; + new->lastRotated.tm_year = now.tm_year; + new->lastRotated.tm_isdst = now.tm_isdst; + + /* fill in the rest of the new->lastRotated fields */ + lr_time = mktime(&new->lastRotated); + new->lastRotated = *localtime(&lr_time); + + return new; +} + +static struct logState *findState(const char *fn) +{ + unsigned int i = hashIndex(fn); + struct logState *p; + + for (p = states[i]->head.lh_first; p != NULL; p = p->list.le_next) + if (!strcmp(fn, p->fn)) + break; + + /* new state */ + if (p == NULL) { + if ((p = newState(fn)) == NULL) + return NULL; + + LIST_INSERT_HEAD(&(states[i]->head), p, list); + } + + return p; +} + +static int runScript(struct logInfo *log, char *logfn, char *script) +{ + int rc; + + if (debug) { + message(MESS_DEBUG, "running script with arg %s: \"%s\"\n", + logfn, script); + return 0; + } + + if (!FORK_OR_VFORK()) { + if (log->flags & LOG_FLAG_SU) { + if (switch_user_back_permanently() != 0) { + DOEXIT(1); + } + } + execl("/system/bin/sh", "sh", "-c", script, "logrotate_script", logfn, NULL); + DOEXIT(1); + } + + wait(&rc); + return rc; +} + +int createOutputFile(char *fileName, int flags, struct stat *sb, acl_type acl, int force_mode) +{ + int fd = -1; + struct stat sb_create; + int acl_set = 0; + int i; + + for (i = 0; i < 2; ++i) { + fd = open(fileName, (flags | O_EXCL | O_NOFOLLOW), + (S_IRUSR | S_IWUSR) & sb->st_mode); + + if ((fd >= 0) || (errno != EEXIST)) + break; + + /* the destination file already exists, while it should not. It may caused by a failed compression */ + message(MESS_ERROR, "destination %s already exists, delete it...\n", fileName); + if (unlink(fileName) != 0) { + message(MESS_ERROR, "error deleting already existing output file" + " %s: %s\n", fileName, strerror(errno)); + return -1; + } + /* existing file deleted, try it once again */ + } + + if (fd < 0) { + message(MESS_ERROR, "error creating output file %s: %s\n", + fileName, strerror(errno)); + return -1; + } + if (fchmod(fd, (S_IRUSR | S_IWUSR) & sb->st_mode)) { + message(MESS_ERROR, "error setting mode of %s: %s\n", + fileName, strerror(errno)); + close(fd); + return -1; + } + + if (fstat(fd, &sb_create)) { + message(MESS_ERROR, "fstat of %s failed: %s\n", fileName, + strerror(errno)); + close(fd); + return -1; + } + + if ((sb_create.st_uid != sb->st_uid || sb_create.st_gid != sb->st_gid) && + fchown(fd, sb->st_uid, sb->st_gid)) { + message(MESS_ERROR, "error setting owner of %s to uid %d and gid %d: %s\n", + fileName, sb->st_uid, sb->st_gid, strerror(errno)); + close(fd); + return -1; + } + +#ifdef WITH_ACL + if (!force_mode && acl) { + if (acl_set_fd(fd, acl) == -1) { + if (!ACL_NOT_WELL_SUPPORTED(errno)) { + message(MESS_ERROR, "setting ACL for %s: %s\n", + fileName, strerror(errno)); + close(fd); + return -1; + } + acl_set = 0; + } + else { + acl_set = 1; + } + } +#endif + + if (!acl_set || force_mode) { + if (fchmod(fd, sb->st_mode)) { + message(MESS_ERROR, "error setting mode of %s: %s\n", + fileName, strerror(errno)); + close(fd); + return -1; + } + } + + return fd; +} + +#define DIGITS 10 + +/* unlink, but try to call shred from GNU fileutils */ +static int shred_file(int fd, char *filename, struct logInfo *log) +{ + char count[DIGITS]; /* that's a lot of shredding :) */ + const char **fullCommand; + int id = 0; + int status; + + if (log->preremove) { + message(MESS_DEBUG, "running preremove script\n"); + if (runScript(log, filename, log->preremove)) { + message(MESS_ERROR, + "error running preremove script " + "for %s of '%s'. Not removing this file.\n", + filename, log->pattern); + /* What ever was supposed to happen did not happen, + * therefore do not unlink the file yet. */ + return 1; + } + } + + if (!(log->flags & LOG_FLAG_SHRED)) { + return unlink(filename); + } + + message(MESS_DEBUG, "Using shred to remove the file %s\n", filename); + + if (log->shred_cycles != 0) { + fullCommand = alloca(sizeof(*fullCommand) * 6); + } + else { + fullCommand = alloca(sizeof(*fullCommand) * 4); + } + fullCommand[id++] = "shred"; + fullCommand[id++] = "-u"; + + if (log->shred_cycles != 0) { + fullCommand[id++] = "-n"; + snprintf(count, DIGITS - 1, "%d", log->shred_cycles); + fullCommand[id++] = count; + } + fullCommand[id++] = "-"; + fullCommand[id++] = NULL; + + if (!FORK_OR_VFORK()) { + dup2(fd, 1); + close(fd); + + if (switch_user_permanently(log) != 0) { + DOEXIT(1); + } + + execvp(fullCommand[0], (void *) fullCommand); + DOEXIT(1); + } + + wait(&status); + + if (!WIFEXITED(status) || WEXITSTATUS(status)) { + message(MESS_ERROR, "Failed to shred %s\n, trying unlink", filename); + return unlink(filename); + } + + /* We have to unlink it after shred anyway, + * because it doesn't remove the file itself */ + return unlink(filename); +} + +static int removeLogFile(char *name, struct logInfo *log) +{ + int fd; + message(MESS_DEBUG, "removing old log %s\n", name); + + if ((fd = open(name, O_RDWR | O_NOFOLLOW)) < 0) { + message(MESS_ERROR, "error opening %s: %s\n", + name, strerror(errno)); + return 1; + } + + if (!debug && shred_file(fd, name, log)) { + message(MESS_ERROR, "Failed to remove old log %s: %s\n", + name, strerror(errno)); + close(fd); + return 1; + } + + close(fd); + return 0; +} + +static int compressLogFile(char *name, struct logInfo *log, struct stat *sb) +{ + char *compressedName; + char *envInFilename; + const char **fullCommand; + struct utimbuf utim; + int inFile; + int outFile; + int i; + int status; + int compressPipe[2]; + char buff[4092]; + int error_printed = 0; + void *prevCtx; + + message(MESS_DEBUG, "compressing log with: %s\n", log->compress_prog); + if (debug) + return 0; + + fullCommand = alloca(sizeof(*fullCommand) * + (log->compress_options_count + 2)); + fullCommand[0] = log->compress_prog; + for (i = 0; i < log->compress_options_count; i++) + fullCommand[i + 1] = log->compress_options_list[i]; + fullCommand[log->compress_options_count + 1] = NULL; + + compressedName = alloca(strlen(name) + strlen(log->compress_ext) + 2); + sprintf(compressedName, "%s%s", name, log->compress_ext); + + if ((inFile = open(name, O_RDWR | O_NOFOLLOW)) < 0) { + message(MESS_ERROR, "unable to open %s for compression\n", name); + return 1; + } + + if (setSecCtx(inFile, name, &prevCtx) != 0) { + /* error msg already printed */ + close(inFile); + return 1; + } + +#ifdef WITH_ACL + if ((prev_acl = acl_get_fd(inFile)) == NULL) { + if (!ACL_NOT_WELL_SUPPORTED(errno)) { + message(MESS_ERROR, "getting file ACL %s: %s\n", + name, strerror(errno)); + restoreSecCtx(&prevCtx); + close(inFile); + return 1; + } + } +#endif + + outFile = + createOutputFile(compressedName, O_RDWR | O_CREAT, sb, prev_acl, 0); + restoreSecCtx(&prevCtx); +#ifdef WITH_ACL + if (prev_acl) { + acl_free(prev_acl); + prev_acl = NULL; + } +#endif + if (outFile < 0) { + close(inFile); + return 1; + } + + if (pipe(compressPipe) < 0) { + message(MESS_ERROR, "error opening pipe for compress: %s", + strerror(errno)); + close(inFile); + close(outFile); + return 1; + } + + if (!FORK_OR_VFORK()) { + dup2(inFile, 0); + close(inFile); + dup2(outFile, 1); + close(outFile); + dup2(compressPipe[1], 2); + close(compressPipe[0]); + close(compressPipe[1]); + + if (switch_user_permanently(log) != 0) { + DOEXIT(1); + } + + envInFilename = alloca(strlen("LOGROTATE_COMPRESSED_FILENAME=") + strlen(name) + 2); + sprintf(envInFilename, "LOGROTATE_COMPRESSED_FILENAME=%s", name); + putenv(envInFilename); + execvp(fullCommand[0], (void *) fullCommand); + DOEXIT(1); + } + + close(compressPipe[1]); + while ((i = read(compressPipe[0], buff, sizeof(buff) - 1)) > 0) { + if (!error_printed) { + error_printed = 1; + message(MESS_ERROR, "Compressing program wrote following message " + "to stderr when compressing log %s:\n", name); + } + buff[i] = '\0'; + fprintf(stderr, "%s", buff); + } + close(compressPipe[0]); + wait(&status); + + fsync(outFile); + close(outFile); + + if (!WIFEXITED(status) || WEXITSTATUS(status)) { + message(MESS_ERROR, "failed to compress log %s\n", name); + close(inFile); + unlink(compressedName); + return 1; + } + + utim.actime = sb->st_atime; + utim.modtime = sb->st_mtime; + utime(compressedName,&utim); + /* If we can't change atime/mtime, it's not a disaster. + It might possibly fail under SELinux. */ + + shred_file(inFile, name, log); + close(inFile); + + return 0; +} + +static int mailLog(struct logInfo *log, char *logFile, char *mailCommand, + char *uncompressCommand, char *address, char *subject) +{ + int mailInput; + pid_t mailChild, uncompressChild = 0; + int mailStatus, uncompressStatus; + int uncompressPipe[2]; + char *mailArgv[] = { mailCommand, "-s", subject, address, NULL }; + int rc = 0; + + if ((mailInput = open(logFile, O_RDONLY | O_NOFOLLOW)) < 0) { + message(MESS_ERROR, "failed to open %s for mailing: %s\n", logFile, + strerror(errno)); + return 1; + } + + if (uncompressCommand) { + if (pipe(uncompressPipe) < 0) { + message(MESS_ERROR, "error opening pipe for uncompress: %s", + strerror(errno)); + close(mailInput); + return 1; + } + if (!(uncompressChild = FORK_OR_VFORK())) { + /* uncompress child */ + dup2(mailInput, 0); + close(mailInput); + dup2(uncompressPipe[1], 1); + close(uncompressPipe[0]); + close(uncompressPipe[1]); + + if (switch_user_permanently(log) != 0) { + DOEXIT(1); + } + + execlp(uncompressCommand, uncompressCommand, NULL); + DOEXIT(1); + } + + close(mailInput); + mailInput = uncompressPipe[0]; + close(uncompressPipe[1]); + } + + if (!(mailChild = FORK_OR_VFORK())) { + dup2(mailInput, 0); + close(mailInput); + close(1); + + // mail command runs as root + if (log->flags & LOG_FLAG_SU) { + if (switch_user_back_permanently() != 0) { + DOEXIT(1); + } + } + + execvp(mailArgv[0], mailArgv); + DOEXIT(1); + } + + close(mailInput); + + waitpid(mailChild, &mailStatus, 0); + + if (!WIFEXITED(mailStatus) || WEXITSTATUS(mailStatus)) { + message(MESS_ERROR, "mail command failed for %s\n", logFile); + rc = 1; + } + + if (uncompressCommand) { + waitpid(uncompressChild, &uncompressStatus, 0); + + if (!WIFEXITED(uncompressStatus) || WEXITSTATUS(uncompressStatus)) { + message(MESS_ERROR, "uncompress command failed mailing %s\n", + logFile); + rc = 1; + } + } + + return rc; +} + +static int mailLogWrapper(char *mailFilename, char *mailCommand, + int logNum, struct logInfo *log) +{ + /* if the log is compressed (and we're not mailing a + * file whose compression has been delayed), we need + * to uncompress it */ + if ((log->flags & LOG_FLAG_COMPRESS) && !(log->flags & LOG_FLAG_DELAYCOMPRESS)) { + if (mailLog(log, mailFilename, mailCommand, + log->uncompress_prog, log->logAddress, + (log->flags & LOG_FLAG_MAILFIRST) ? log->files[logNum] : mailFilename)) + return 1; + } else { + if (mailLog(log, mailFilename, mailCommand, NULL, + log->logAddress, mailFilename)) + return 1; + } + return 0; +} + +/* Use a heuristic to determine whether stat buffer SB comes from a file + with sparse blocks. If the file has fewer blocks than would normally + be needed for a file of its size, then at least one of the blocks in + the file is a hole. In that case, return true. */ +static int is_probably_sparse(struct stat const *sb) +{ +#if defined(HAVE_STRUCT_STAT_ST_BLOCKS) && defined(HAVE_STRUCT_STAT_ST_BLKSIZE) + return (S_ISREG (sb->st_mode) + && sb->st_blocks < sb->st_size / sb->st_blksize); +#else + return 0; +#endif +} + +#define MIN(a,b) ((a) < (b) ? (a) : (b)) + +/* Return whether the buffer consists entirely of NULs. + Note the word after the buffer must be non NUL. */ + +static inline int is_nul (void const *buf, size_t bufsize) +{ + char const *cbuf = buf; + char const *cp = buf; + + /* Find the first nonzero *byte*, or the sentinel. */ + while (*cp++ == 0) + continue; + + return cbuf + bufsize < cp; +} + +static size_t full_write(int fd, const void *buf, size_t count) +{ + size_t total = 0; + const char *ptr = (const char *) buf; + + while (count > 0) + { + size_t n_rw; + for (;;) + { + n_rw = write (fd, buf, count); + if (errno == EINTR) + continue; + else + break; + } + if (n_rw == (size_t) -1) + break; + if (n_rw == 0) + break; + total += n_rw; + ptr += n_rw; + count -= n_rw; + } + + return total; +} + +static int sparse_copy(int src_fd, int dest_fd, struct stat *sb, + const char *saveLog, const char *currLog) +{ + int make_holes = is_probably_sparse(sb); + size_t max_n_read = SIZE_MAX; + int last_write_made_hole = 0; + off_t total_n_read = 0; + char buf[BUFSIZ + 1]; + + while (max_n_read) { + int make_hole = 0; + + ssize_t n_read = read (src_fd, buf, MIN (max_n_read, BUFSIZ)); + if (n_read < 0) { + if (errno == EINTR) { + continue; + } + message(MESS_ERROR, "error reading %s: %s\n", + currLog, strerror(errno)); + return 0; + } + + if (n_read == 0) + break; + + max_n_read -= n_read; + total_n_read += n_read; + + if (make_holes) { + /* Sentinel required by is_nul(). */ + buf[n_read] = '\1'; + + if ((make_hole = is_nul(buf, n_read))) { + if (lseek (dest_fd, n_read, SEEK_CUR) < 0) { + message(MESS_ERROR, "error seeking %s: %s\n", + saveLog, strerror(errno)); + return 0; + } + } + } + + if (!make_hole) { + size_t n = n_read; + if (full_write (dest_fd, buf, n) != n) { + message(MESS_ERROR, "error writing to %s: %s\n", + saveLog, strerror(errno)); + return 0; + } + } + + last_write_made_hole = make_hole; + } + + if (last_write_made_hole) { + if (ftruncate(dest_fd, total_n_read) < 0) { + message(MESS_ERROR, "error ftruncate %s: %s\n", + saveLog, strerror(errno)); + return 0; + } + } + + return 1; +} + +static int copyTruncate(char *currLog, char *saveLog, struct stat *sb, + int flags) +{ + int fdcurr = -1, fdsave = -1; + void *prevCtx; + + message(MESS_DEBUG, "copying %s to %s\n", currLog, saveLog); + + if (!debug) { + if ((fdcurr = open(currLog, ((flags & LOG_FLAG_COPY) ? O_RDONLY : O_RDWR) | O_NOFOLLOW)) < 0) { + message(MESS_ERROR, "error opening %s: %s\n", currLog, + strerror(errno)); + return 1; + } + + if (setSecCtx(fdcurr, currLog, &prevCtx) != 0) { + /* error msg already printed */ + close(fdcurr); + return 1; + } +#ifdef WITH_ACL + if ((prev_acl = acl_get_fd(fdcurr)) == NULL) { + if (!ACL_NOT_WELL_SUPPORTED(errno)) { + message(MESS_ERROR, "getting file ACL %s: %s\n", + currLog, strerror(errno)); + restoreSecCtx(&prevCtx); + close(fdcurr); + return 1; + } + } +#endif /* WITH_ACL */ + fdsave = + createOutputFile(saveLog, O_WRONLY | O_CREAT, sb, prev_acl, 0); + restoreSecCtx(&prevCtx); +#ifdef WITH_ACL + if (prev_acl) { + acl_free(prev_acl); + prev_acl = NULL; + } +#endif + if (fdsave < 0) { + close(fdcurr); + return 1; + } + + if (sparse_copy(fdcurr, fdsave, sb, saveLog, currLog) != 1) { + close(fdcurr); + close(fdsave); + message(MESS_ERROR, "error copying %s to %s: %s\n", currLog, + saveLog, strerror(errno)); + unlink(saveLog); + return 1; + } + } + + if (flags & LOG_FLAG_COPYTRUNCATE) { + message(MESS_DEBUG, "truncating %s\n", currLog); + + if (!debug) { + fsync(fdsave); + if (ftruncate(fdcurr, 0)) { + message(MESS_ERROR, "error truncating %s: %s\n", currLog, + strerror(errno)); + close(fdcurr); + close(fdsave); + return 1; + } + } + } else + message(MESS_DEBUG, "Not truncating %s\n", currLog); + + if (fdcurr >= 0) { + close(fdcurr); + } + if (fdsave >= 0) { + close(fdsave); + } + return 0; +} + +int findNeedRotating(struct logInfo *log, int logNum, int force) +{ + struct stat sb; + struct logState *state = NULL; + struct tm now = *localtime(&nowSecs); + + message(MESS_DEBUG, "considering log %s\n", log->files[logNum]); + + /* Check if parent directory of this log has safe permissions */ + if ((log->flags & LOG_FLAG_SU) == 0 && getuid() == 0) { + char *ld = ourDirName(log->files[logNum]); + if (stat(ld, &sb)) { + /* If parent directory doesn't exist, it's not real error + (unless nomissingok is specified) + and rotation is not needed */ + if (errno != ENOENT || (errno == ENOENT && (log->flags & LOG_FLAG_MISSINGOK) == 0)) { + message(MESS_ERROR, "stat of %s failed: %s\n", ld, + strerror(errno)); + free(ld); + return 1; + } + free(ld); + return 0; + } + /* Don't rotate in directories writable by others or group which is not "root" */ + if ((sb.st_gid != 0 && sb.st_mode & S_IWGRP) || sb.st_mode & S_IWOTH) { + message(MESS_ERROR, "skipping \"%s\" because parent directory has insecure permissions" + " (It's world writable or writable by group which is not \"root\")" + " Set \"su\" directive in config file to tell logrotate which user/group" + " should be used for rotation.\n" + ,log->files[logNum]); + free(ld); + return 1; + } + free(ld); + } + + if (lstat(log->files[logNum], &sb)) { + if ((log->flags & LOG_FLAG_MISSINGOK) && (errno == ENOENT)) { + message(MESS_DEBUG, " log %s does not exist -- skipping\n", + log->files[logNum]); + return 0; + } + message(MESS_ERROR, "stat of %s failed: %s\n", log->files[logNum], + strerror(errno)); + return 1; + } + + state = findState(log->files[logNum]); + state->doRotate = 0; + state->sb = sb; + state->isUsed = 1; + + if ((sb.st_mode & S_IFMT) == S_IFLNK) { + message(MESS_DEBUG, " log %s is symbolic link. Rotation of symbolic" + " links is not allowed to avoid security issues -- skipping.\n", + log->files[logNum]); + return 0; + } + + message(MESS_DEBUG, " Now: %d-%02d-%02d %02d:%02d\n", 1900 + now.tm_year, + 1 + now.tm_mon, now.tm_mday, + now.tm_hour, now.tm_min); + + message(MESS_DEBUG, " Last rotated at %d-%02d-%02d %02d:%02d\n", 1900 + state->lastRotated.tm_year, + 1 + state->lastRotated.tm_mon, state->lastRotated.tm_mday, + state->lastRotated.tm_hour, state->lastRotated.tm_min); + + if (force) { + /* user forced rotation of logs from command line */ + state->doRotate = 1; + } + else if (log->maxsize && sb.st_size > log->maxsize) { + state->doRotate = 1; + } + else if (log->criterium == ROT_SIZE) { + state->doRotate = (sb.st_size >= log->threshhold); + if (!state->doRotate) { + message(MESS_DEBUG, " log does not need rotating " + "(log size is below the 'size' threshold)\n"); + } + } else if (mktime(&state->lastRotated) - mktime(&now) > (25 * 3600)) { + /* 25 hours allows for DST changes as well as geographical moves */ + message(MESS_ERROR, + "log %s last rotated in the future -- rotation forced\n", + log->files[logNum]); + state->doRotate = 1; + } else if (state->lastRotated.tm_year != now.tm_year || + state->lastRotated.tm_mon != now.tm_mon || + state->lastRotated.tm_mday != now.tm_mday || + state->lastRotated.tm_hour != now.tm_hour) { + switch (log->criterium) { + case ROT_WEEKLY: + /* rotate if: + 1) the current weekday is before the weekday of the + last rotation + 2) more then a week has passed since the last + rotation */ + state->doRotate = ((now.tm_wday < state->lastRotated.tm_wday) + || + ((mktime(&now) - + mktime(&state->lastRotated)) > + (7 * 24 * 3600))); + if (!state->doRotate) { + message(MESS_DEBUG, " log does not need rotating " + "(log has been rotated at %d-%d-%d %d:%d, " + "that is not week ago yet)\n", 1900 + state->lastRotated.tm_year, + 1 + state->lastRotated.tm_mon, state->lastRotated.tm_mday, + state->lastRotated.tm_hour, state->lastRotated.tm_min); + } + break; + case ROT_HOURLY: + state->doRotate = ((now.tm_hour != state->lastRotated.tm_hour) || + (now.tm_mday != state->lastRotated.tm_mday) || + (now.tm_mon != state->lastRotated.tm_mon) || + (now.tm_year != state->lastRotated.tm_year)); + if (!state->doRotate) { + message(MESS_DEBUG, " log does not need rotating " + "(log has been rotated at %d-%d-%d %d:%d, " + "that is not hour ago yet)\n", 1900 + state->lastRotated.tm_year, + 1 + state->lastRotated.tm_mon, state->lastRotated.tm_mday, + state->lastRotated.tm_hour, state->lastRotated.tm_min); + } + break; + case ROT_DAYS: + /* FIXME: only days=1 is implemented!! */ + state->doRotate = ((now.tm_mday != state->lastRotated.tm_mday) || + (now.tm_mon != state->lastRotated.tm_mon) || + (now.tm_year != state->lastRotated.tm_year)); + if (!state->doRotate) { + message(MESS_DEBUG, " log does not need rotating " + "(log has been rotated at %d-%d-%d %d:%d, " + "that is not day ago yet)\n", 1900 + state->lastRotated.tm_year, + 1 + state->lastRotated.tm_mon, state->lastRotated.tm_mday, + state->lastRotated.tm_hour, state->lastRotated.tm_min); + } + break; + case ROT_MONTHLY: + /* rotate if the logs haven't been rotated this month or + this year */ + state->doRotate = ((now.tm_mon != state->lastRotated.tm_mon) || + (now.tm_year != state->lastRotated.tm_year)); + if (!state->doRotate) { + message(MESS_DEBUG, " log does not need rotating " + "(log has been rotated at %d-%d-%d %d:%d, " + "that is not month ago yet)\n", 1900 + state->lastRotated.tm_year, + 1 + state->lastRotated.tm_mon, state->lastRotated.tm_mday, + state->lastRotated.tm_hour, state->lastRotated.tm_min); + } + break; + case ROT_YEARLY: + /* rotate if the logs haven't been rotated this year */ + state->doRotate = (now.tm_year != state->lastRotated.tm_year); + if (!state->doRotate) { + message(MESS_DEBUG, " log does not need rotating " + "(log has been rotated at %d-%d-%d %d:%d, " + "that is not year ago yet)\n", 1900 + state->lastRotated.tm_year, + 1 + state->lastRotated.tm_mon, state->lastRotated.tm_mday, + state->lastRotated.tm_hour, state->lastRotated.tm_min); + } + break; + default: + /* ack! */ + state->doRotate = 0; + break; + } + if (log->minsize && sb.st_size < log->minsize) { + state->doRotate = 0; + message(MESS_DEBUG, " log does not need rotating " + "('minsize' directive is used and the log " + "size is smaller than the minsize value)\n"); + } + if (log->rotateMinAge && log->rotateMinAge >= (nowSecs - sb.st_mtime) / DAY_SECONDS) { + state->doRotate = 0; + message(MESS_DEBUG, " log does not need rotating " + "('minage' directive is used and the log " + "age is smaller than the minage days)\n"); + } + } + else if (!state->doRotate) { + message(MESS_DEBUG, " log does not need rotating " + "(log has been already rotated)\n"); + } + + /* The notifempty flag overrides the normal criteria */ + if (state->doRotate && !(log->flags & LOG_FLAG_IFEMPTY) && !sb.st_size) { + state->doRotate = 0; + message(MESS_DEBUG, " log does not need rotating " + "(log is empty)\n"); + } + + if (state->doRotate) { + message(MESS_DEBUG, " log needs rotating\n"); + } + + return 0; +} + +int prerotateSingleLog(struct logInfo *log, int logNum, struct logState *state, + struct logNames *rotNames) +{ + struct tm now = *localtime(&nowSecs); + char *oldName, *newName = NULL; + char *compext = ""; + char *fileext = ""; + int hasErrors = 0; + int i, j; + char *glob_pattern; + glob_t globResult; + int rc; + int rotateCount = log->rotateCount ? log->rotateCount : 1; + int logStart = (log->logStart == -1) ? 1 : log->logStart; +#define DATEEXT_LEN 64 +#define PATTERN_LEN (DATEEXT_LEN * 2) + char dext_str[DATEEXT_LEN]; + char dformat[DATEEXT_LEN] = ""; + char dext_pattern[PATTERN_LEN]; + char *dext; + + if (!state->doRotate) + return 0; + + /* Logs with rotateCounts of 0 are rotated once, then removed. This + lets scripts run properly, and everything gets mailed properly. */ + + message(MESS_DEBUG, "rotating log %s, log->rotateCount is %d\n", + log->files[logNum], log->rotateCount); + + if (log->flags & LOG_FLAG_COMPRESS) + compext = log->compress_ext; + + state->lastRotated = now; + + if (log->oldDir) { + if (log->oldDir[0] != '/') { + char *ld = ourDirName(log->files[logNum]); + rotNames->dirName = + malloc(strlen(ld) + strlen(log->oldDir) + 2); + sprintf(rotNames->dirName, "%s/%s", ld, log->oldDir); + free(ld); + } else + rotNames->dirName = strdup(log->oldDir); + } else + rotNames->dirName = ourDirName(log->files[logNum]); + + rotNames->baseName = strdup(ourBaseName(log->files[logNum])); + + if (log->addextension) { + size_t baseLen = strlen(rotNames->baseName); + size_t extLen = strlen(log->addextension); + if (baseLen >= extLen && + strncmp(&(rotNames->baseName[baseLen - extLen]), + log->addextension, extLen) == 0) { + char *tempstr; + + tempstr = calloc(baseLen - extLen + 1, sizeof(char)); + strncat(tempstr, rotNames->baseName, baseLen - extLen); + free(rotNames->baseName); + rotNames->baseName = tempstr; + } + fileext = log->addextension; + } + + if (log->extension && + strncmp(& + (rotNames-> + baseName[strlen(rotNames->baseName) - + strlen(log->extension)]), log->extension, + strlen(log->extension)) == 0) { + char *tempstr; + + fileext = log->extension; + tempstr = + calloc(strlen(rotNames->baseName) - strlen(log->extension) + 1, + sizeof(char)); + strncat(tempstr, rotNames->baseName, + strlen(rotNames->baseName) - strlen(log->extension)); + free(rotNames->baseName); + rotNames->baseName = tempstr; + } + + /* Adjust "now" if we want yesterday's date */ + if (log->flags & LOG_FLAG_DATEYESTERDAY) { + now.tm_hour = 12; /* set hour to noon to work around DST issues */ + now.tm_mday = now.tm_mday - 1; + mktime(&now); + } + + /* Allow only %Y %d %m and create valid strftime format string + * Construct the glob pattern corresponding to the date format */ + dext_str[0] = '\0'; + if (log->dateformat) { + i = j = 0; + memset(dext_pattern, 0, sizeof(dext_pattern)); + dext = log->dateformat; + while (*dext == ' ') + dext++; + while ((*dext != '\0') && (!hasErrors)) { + /* Will there be a space for a char and '\0'? */ + if (j >= (sizeof(dext_pattern) - 1)) { + message(MESS_ERROR, "Date format %s is too long\n", + log->dateformat); + hasErrors = 1; + break; + } + if (*dext == '%') { + switch (*(dext + 1)) { + case 'Y': + strncat(dext_pattern, "[0-9][0-9]", + sizeof(dext_pattern) - strlen(dext_pattern) - 1); + j += 10; /* strlen("[0-9][0-9]") */ + case 'm': + case 'd': + case 'H': + case 'M': + case 'S': + case 'V': + strncat(dext_pattern, "[0-9][0-9]", + sizeof(dext_pattern) - strlen(dext_pattern) - 1); + j += 10; + if (j >= (sizeof(dext_pattern) - 1)) { + message(MESS_ERROR, "Date format %s is too long\n", + log->dateformat); + hasErrors = 1; + break; + } + dformat[i++] = *(dext++); + dformat[i] = *dext; + break; + case 's': + /* End of year 2293 this pattern does not work. */ + strncat(dext_pattern, + "[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]", + sizeof(dext_pattern) - strlen(dext_pattern) - 1); + j += 50; + if (j >= (sizeof(dext_pattern) - 1)) { + message(MESS_ERROR, "Date format %s is too long\n", + log->dateformat); + hasErrors = 1; + break; + } + dformat[i++] = *(dext++); + dformat[i] = *dext; + break; + default: + dformat[i++] = *dext; + dformat[i] = '%'; + dext_pattern[j++] = *dext; + break; + } + } else { + dformat[i] = *dext; + dext_pattern[j++] = *dext; + } + ++i; + ++dext; + } + dformat[i] = '\0'; + message(MESS_DEBUG, "Converted '%s' -> '%s'\n", log->dateformat, dformat); + strftime(dext_str, sizeof(dext_str), dformat, &now); + } else { + if (log->criterium == ROT_HOURLY) { + /* hourly adds another two digits */ + strftime(dext_str, sizeof(dext_str), "-%Y%m%d%H", &now); + strncpy(dext_pattern, "-[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]", + sizeof(dext_pattern)); + } else { + /* The default dateformat and glob pattern */ + strftime(dext_str, sizeof(dext_str), "-%Y%m%d", &now); + strncpy(dext_pattern, "-[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]", + sizeof(dext_pattern)); + } + dext_pattern[PATTERN_LEN - 1] = '\0'; + } + message(MESS_DEBUG, "dateext suffix '%s'\n", dext_str); + message(MESS_DEBUG, "glob pattern '%s'\n", dext_pattern); + + if (setSecCtxByName(log->files[logNum], (void **) &prev_context) != 0) { + /* error msg already printed */ + return 1; + } + + /* First compress the previous log when necessary */ + if (log->flags & LOG_FLAG_COMPRESS && + log->flags & LOG_FLAG_DELAYCOMPRESS) { + if (log->flags & LOG_FLAG_DATEEXT) { + /* glob for uncompressed files with our pattern */ + if (asprintf(&glob_pattern, "%s/%s%s%s", rotNames->dirName, + rotNames->baseName, dext_pattern, fileext) < 0) { + message(MESS_FATAL, "could not allocate glob pattern memory\n"); + } + rc = glob(glob_pattern, 0, globerr, &globResult); + if (!rc && globResult.gl_pathc > 0) { + sortGlobResult(&globResult, strlen(rotNames->dirName) + 1 + strlen(rotNames->baseName), dformat); + for (i = 0; i < globResult.gl_pathc && !hasErrors; i++) { + struct stat sbprev; + + if (asprintf(&oldName, "%s", (globResult.gl_pathv)[i]) < 0) { + message(MESS_FATAL, "could not allocate glob result memory\n"); + } + if (stat(oldName, &sbprev)) { + message(MESS_DEBUG, + "previous log %s does not exist\n", + oldName); + } else { + hasErrors = compressLogFile(oldName, log, &sbprev); + } + free(oldName); + } + } else { + message(MESS_DEBUG, + "glob finding logs to compress failed\n"); + /* fallback to old behaviour */ + if (asprintf(&oldName, "%s/%s.%d%s", rotNames->dirName, + rotNames->baseName, logStart, fileext) < 0) { + message(MESS_FATAL, "could not allocate oldName memory\n"); + } + free(oldName); + } + globfree(&globResult); + free(glob_pattern); + } else { + struct stat sbprev; + if (asprintf(&oldName, "%s/%s.%d%s", rotNames->dirName, + rotNames->baseName, logStart, fileext) < 0) { + message(MESS_FATAL, "could not allocate oldName memory\n"); + } + if (stat(oldName, &sbprev)) { + message(MESS_DEBUG, "previous log %s does not exist\n", + oldName); + } else { + hasErrors = compressLogFile(oldName, log, &sbprev); + } + free(oldName); + } + } + + /* adding 2 due to / and \0 being added by snprintf */ + rotNames->firstRotated = + malloc(strlen(rotNames->dirName) + strlen(rotNames->baseName) + + strlen(fileext) + strlen(compext) + DATEEXT_LEN + 2 ); + + if (log->flags & LOG_FLAG_DATEEXT) { + /* glob for compressed files with our pattern + * and compress ext */ + if (asprintf(&glob_pattern, "%s/%s%s%s%s", rotNames->dirName, + rotNames->baseName, dext_pattern, fileext, compext) < 0) { + message(MESS_ERROR, "could not allocate glob pattern memory\n"); + } + rc = glob(glob_pattern, 0, globerr, &globResult); + if (!rc) { + /* search for files to drop, if we find one remember it, + * if we find another one mail and remove the first and + * remember the second and so on */ + struct stat fst_buf; + int mail_out = -1; + /* remove the first (n - rotateCount) matches + * no real rotation needed, since the files have + * the date in their name */ + sortGlobResult(&globResult, strlen(rotNames->dirName) + 1 + strlen(rotNames->baseName), dformat); + for (i = 0; i < globResult.gl_pathc; i++) { + if (!stat((globResult.gl_pathv)[i], &fst_buf)) { + if ((i <= ((int) globResult.gl_pathc - rotateCount)) + || ((log->rotateAge > 0) + && + (((nowSecs - fst_buf.st_mtime) / DAY_SECONDS) + > log->rotateAge))) { + if (mail_out != -1) { + char *mailFilename = + (globResult.gl_pathv)[mail_out]; + if (!hasErrors && log->logAddress) + hasErrors = + mailLogWrapper(mailFilename, + mailCommand, logNum, + log); + if (!hasErrors) { + message(MESS_DEBUG, "removing %s\n", mailFilename); + hasErrors = removeLogFile(mailFilename, log); + } + } + mail_out = i; + } + } + } + if (mail_out != -1) { + /* oldName is oldest Backup found (for unlink later) */ + if (asprintf(&oldName, "%s", (globResult.gl_pathv)[mail_out]) < 0) { + message(MESS_FATAL, "could not allocate mailout memory\n"); + } + rotNames->disposeName = malloc(strlen(oldName)+1); + strcpy(rotNames->disposeName, oldName); + free(oldName); + } else { + free(rotNames->disposeName); + rotNames->disposeName = NULL; + } + } else { + message(MESS_DEBUG, "glob finding old rotated logs failed\n"); + free(rotNames->disposeName); + rotNames->disposeName = NULL; + } + /* firstRotated is most recently created/compressed rotated log */ + sprintf(rotNames->firstRotated, "%s/%s%s%s%s", + rotNames->dirName, rotNames->baseName, dext_str, fileext, + (log->flags & LOG_FLAG_DELAYCOMPRESS) ? "" : compext); + globfree(&globResult); + free(glob_pattern); + } else { + if (log->rotateAge) { + struct stat fst_buf; + for (i = 1; i <= rotateCount + 1; i++) { + if (asprintf(&oldName, "%s/%s.%d%s%s", rotNames->dirName, + rotNames->baseName, i, fileext, compext) < 0) { + message(MESS_FATAL, "could not allocate mailFilename memory\n"); + } + if (!stat(oldName, &fst_buf) + && (((nowSecs - fst_buf.st_mtime) / DAY_SECONDS) + > log->rotateAge)) { + char *mailFilename = oldName; + if (!hasErrors && log->logAddress) + hasErrors = + mailLogWrapper(mailFilename, mailCommand, + logNum, log); + if (!hasErrors) + hasErrors = removeLogFile(mailFilename, log); + } + free(oldName); + } + } + + if (asprintf(&oldName, "%s/%s.%d%s%s", rotNames->dirName, + rotNames->baseName, logStart + rotateCount, fileext, + compext) < 0) { + message(MESS_FATAL, "could not allocate disposeName memory\n"); + } + newName = strdup(oldName); + + rotNames->disposeName = strdup(oldName); + + sprintf(rotNames->firstRotated, "%s/%s.%d%s%s", rotNames->dirName, + rotNames->baseName, logStart, fileext, + (log->flags & LOG_FLAG_DELAYCOMPRESS) ? "" : compext); + + for (i = rotateCount + logStart - 1; (i >= 0) && !hasErrors; i--) { + free(newName); + newName = oldName; + if (asprintf(&oldName, "%s/%s.%d%s%s", rotNames->dirName, + rotNames->baseName, i, fileext, compext) < 0) { + message(MESS_FATAL, "could not allocate oldName memory\n"); + } + + message(MESS_DEBUG, + "renaming %s to %s (rotatecount %d, logstart %d, i %d), \n", + oldName, newName, rotateCount, logStart, i); + + if (!debug && rename(oldName, newName)) { + if (errno == ENOENT) { + message(MESS_DEBUG, "old log %s does not exist\n", + oldName); + } else { + message(MESS_ERROR, "error renaming %s to %s: %s\n", + oldName, newName, strerror(errno)); + hasErrors = 1; + } + } + if (hasErrors || i - 1 < 0) + free(oldName); + + } + free(newName); + } /* !LOG_FLAG_DATEEXT */ + + if (log->flags & LOG_FLAG_DATEEXT) { + char *destFile; + struct stat fst_buf; + + if (asprintf(&(rotNames->finalName), "%s/%s%s%s", rotNames->dirName, + rotNames->baseName, dext_str, fileext) < 0) { + message(MESS_FATAL, "could not allocate finalName memory\n"); + } + if (asprintf(&destFile, "%s%s", rotNames->finalName, compext) < 0) { + message(MESS_FATAL, "could not allocate destFile memory\n"); + } + if (!stat(destFile, &fst_buf)) { + message(MESS_ERROR, + "destination %s already exists, skipping rotation\n", + rotNames->firstRotated); + hasErrors = 1; + } + free(destFile); + } else { + /* note: the gzip extension is *not* used here! */ + if (asprintf(&(rotNames->finalName), "%s/%s.%d%s", rotNames->dirName, + rotNames->baseName, logStart, fileext) < 0) { + message(MESS_ERROR, "could not allocate finalName memory\n"); + } + } + + /* if the last rotation doesn't exist, that's okay */ + if (rotNames->disposeName && access(rotNames->disposeName, F_OK)) { + message(MESS_DEBUG, + "log %s doesn't exist -- won't try to dispose of it\n", + rotNames->disposeName); + free(rotNames->disposeName); + rotNames->disposeName = NULL; + } + + return hasErrors; +} + +int rotateSingleLog(struct logInfo *log, int logNum, struct logState *state, + struct logNames *rotNames) +{ + int hasErrors = 0; + struct stat sb; + int fd; + void *savedContext = NULL; + char *tmpFilename = NULL; + + if (!state->doRotate) + return 0; + + if (!hasErrors) { + + if (!(log->flags & (LOG_FLAG_COPYTRUNCATE | LOG_FLAG_COPY))) { + if (setSecCtxByName(log->files[logNum], &savedContext) != 0) { + /* error msg already printed */ + return 1; + } +#ifdef WITH_ACL + if ((prev_acl = acl_get_file(log->files[logNum], ACL_TYPE_ACCESS)) == NULL) { + if (!ACL_NOT_WELL_SUPPORTED(errno)) { + message(MESS_ERROR, "getting file ACL %s: %s\n", + log->files[logNum], strerror(errno)); + hasErrors = 1; + } + } +#endif /* WITH_ACL */ + if (log->flags & LOG_FLAG_TMPFILENAME) { + if (asprintf(&tmpFilename, "%s%s", log->files[logNum], ".tmp") < 0) { + message(MESS_FATAL, "could not allocate tmpFilename memory\n"); + restoreSecCtx(&savedContext); + return 1; + } + + message(MESS_DEBUG, "renaming %s to %s\n", log->files[logNum], + tmpFilename); + if (!debug && !hasErrors && rename(log->files[logNum], tmpFilename)) { + message(MESS_ERROR, "failed to rename %s to %s: %s\n", + log->files[logNum], tmpFilename, + strerror(errno)); + hasErrors = 1; + } + } + else { + message(MESS_DEBUG, "renaming %s to %s\n", log->files[logNum], + rotNames->finalName); + if (!debug && !hasErrors && + rename(log->files[logNum], rotNames->finalName)) { + message(MESS_ERROR, "failed to rename %s to %s: %s\n", + log->files[logNum], tmpFilename, + strerror(errno)); + hasErrors = 1; + } + } + + if (!log->rotateCount) { + rotNames->disposeName = + realloc(rotNames->disposeName, + strlen(rotNames->dirName) + + strlen(rotNames->baseName) + + strlen(log->files[logNum]) + 10); + sprintf(rotNames->disposeName, "%s%s", rotNames->finalName, + (log->compress_ext + && (log->flags & LOG_FLAG_COMPRESS)) ? + log->compress_ext : ""); + message(MESS_DEBUG, "disposeName will be %s\n", + rotNames->disposeName); + } + } + + if (!hasErrors && log->flags & LOG_FLAG_CREATE && + !(log->flags & (LOG_FLAG_COPYTRUNCATE | LOG_FLAG_COPY))) { + if (log->createUid == NO_UID) + sb.st_uid = state->sb.st_uid; + else + sb.st_uid = log->createUid; + + if (log->createGid == NO_GID) + sb.st_gid = state->sb.st_gid; + else + sb.st_gid = log->createGid; + + int have_create_mode = 0; + if (log->createMode == NO_MODE) + sb.st_mode = state->sb.st_mode & 0777; + else { + sb.st_mode = log->createMode; + have_create_mode = 1; + } + + message(MESS_DEBUG, "creating new %s mode = 0%o uid = %d " + "gid = %d\n", log->files[logNum], (unsigned int) sb.st_mode, + (int) sb.st_uid, (int) sb.st_gid); + + if (!debug) { + if (!hasErrors) { + fd = createOutputFile(log->files[logNum], O_CREAT | O_RDWR, + &sb, prev_acl, have_create_mode); +#ifdef WITH_ACL + if (prev_acl) { + acl_free(prev_acl); + prev_acl = NULL; + } +#endif + if (fd < 0) + hasErrors = 1; + else { + close(fd); + } + } + } + } + + restoreSecCtx(&savedContext); + + if (!hasErrors + && log->flags & (LOG_FLAG_COPYTRUNCATE | LOG_FLAG_COPY) + && !(log->flags & LOG_FLAG_TMPFILENAME)) { + hasErrors = + copyTruncate(log->files[logNum], rotNames->finalName, + &state->sb, log->flags); + } + +#ifdef WITH_ACL + if (prev_acl) { + acl_free(prev_acl); + prev_acl = NULL; + } +#endif /* WITH_ACL */ + + } + return hasErrors; +} + +int postrotateSingleLog(struct logInfo *log, int logNum, struct logState *state, + struct logNames *rotNames) +{ + int hasErrors = 0; + + if (!state->doRotate) { + return 0; + } + + if (!hasErrors && log->flags & LOG_FLAG_TMPFILENAME) { + char *tmpFilename = NULL; + if (asprintf(&tmpFilename, "%s%s", log->files[logNum], ".tmp") < 0) { + message(MESS_FATAL, "could not allocate tmpFilename memory\n"); + return 1; + } + hasErrors = + copyTruncate(tmpFilename, rotNames->finalName, + &state->sb, LOG_FLAG_COPY); + message(MESS_DEBUG, "removing tmp log %s \n", tmpFilename); + if (!debug && !hasErrors) { + unlink(tmpFilename); + } + } + + if (!hasErrors && (log->flags & LOG_FLAG_COMPRESS) && + !(log->flags & LOG_FLAG_DELAYCOMPRESS)) { + hasErrors = compressLogFile(rotNames->finalName, log, &state->sb); + } + + if (!hasErrors && log->logAddress) { + char *mailFilename; + + if (log->flags & LOG_FLAG_MAILFIRST) + mailFilename = rotNames->firstRotated; + else + mailFilename = rotNames->disposeName; + + if (mailFilename) + hasErrors = + mailLogWrapper(mailFilename, mailCommand, logNum, log); + } + + if (!hasErrors && rotNames->disposeName) + hasErrors = removeLogFile(rotNames->disposeName, log); + + restoreSecCtx((void **) &prev_context); + return hasErrors; +} + +int rotateLogSet(struct logInfo *log, int force) +{ + int i, j; + int hasErrors = 0; + int logHasErrors[log->numFiles]; + int numRotated = 0; + struct logState **state; + struct logNames **rotNames; + + + message(MESS_DEBUG, "\nrotating pattern: %s ", log->pattern); + if (force) { + message(MESS_DEBUG, "forced from command line "); + } + else { + switch (log->criterium) { + case ROT_HOURLY: + message(MESS_DEBUG, "hourly "); + break; + case ROT_DAYS: + message(MESS_DEBUG, "after %llu days ", log->threshhold); + break; + case ROT_WEEKLY: + message(MESS_DEBUG, "weekly "); + break; + case ROT_MONTHLY: + message(MESS_DEBUG, "monthly "); + break; + case ROT_YEARLY: + message(MESS_DEBUG, "yearly "); + break; + case ROT_SIZE: + message(MESS_DEBUG, "%llu bytes ", log->threshhold); + break; + } + } + + if (log->rotateCount) + message(MESS_DEBUG, "(%d rotations)\n", log->rotateCount); + else + message(MESS_DEBUG, "(no old logs will be kept)\n"); + + if (log->oldDir) + message(MESS_DEBUG, "olddir is %s, ", log->oldDir); + + if (log->flags & LOG_FLAG_IFEMPTY) + message(MESS_DEBUG, "empty log files are rotated, "); + else + message(MESS_DEBUG, "empty log files are not rotated, "); + + if (log->minsize) + message(MESS_DEBUG, "only log files >= %llu bytes are rotated, ", log->minsize); + + if (log->maxsize) + message(MESS_DEBUG, "log files >= %llu are rotated earlier, ", log->maxsize); + + if (log->rotateMinAge) + message(MESS_DEBUG, "only log files older than %d days are rotated, ", log->rotateMinAge); + + if (log->logAddress) { + message(MESS_DEBUG, "old logs mailed to %s\n", log->logAddress); + } else { + message(MESS_DEBUG, "old logs are removed\n"); + } + + if (log->numFiles == 0) { + message(MESS_DEBUG, "No logs found. Rotation not needed.\n"); + return 0; + } + + if (log->flags & LOG_FLAG_SU) { + if (switch_user(log->suUid, log->suGid) != 0) { + return 1; + } + } + + for (i = 0; i < log->numFiles; i++) { + logHasErrors[i] = findNeedRotating(log, i, force); + hasErrors |= logHasErrors[i]; + + /* sure is a lot of findStating going on .. */ + if ((findState(log->files[i]))->doRotate) + numRotated++; + } + + if (log->first) { + if (!numRotated) { + message(MESS_DEBUG, "not running first action script, " + "since no logs will be rotated\n"); + } else { + message(MESS_DEBUG, "running first action script\n"); + if (runScript(log, log->pattern, log->first)) { + message(MESS_ERROR, "error running first action script " + "for %s\n", log->pattern); + hasErrors = 1; + if (log->flags & LOG_FLAG_SU) { + if (switch_user_back() != 0) { + return 1; + } + } + /* finish early, firstaction failed, affects all logs in set */ + return hasErrors; + } + } + } + + state = malloc(log->numFiles * sizeof(struct logState *)); + rotNames = malloc(log->numFiles * sizeof(struct logNames *)); + + for (j = 0; + (!(log->flags & LOG_FLAG_SHAREDSCRIPTS) && j < log->numFiles) + || ((log->flags & LOG_FLAG_SHAREDSCRIPTS) && j < 1); j++) { + + for (i = j; + ((log->flags & LOG_FLAG_SHAREDSCRIPTS) && i < log->numFiles) + || (!(log->flags & LOG_FLAG_SHAREDSCRIPTS) && i == j); i++) { + state[i] = findState(log->files[i]); + + rotNames[i] = malloc(sizeof(struct logNames)); + memset(rotNames[i], 0, sizeof(struct logNames)); + + logHasErrors[i] |= + prerotateSingleLog(log, i, state[i], rotNames[i]); + hasErrors |= logHasErrors[i]; + } + + if (log->pre + && (!( + ((logHasErrors[j] || !state[j]->doRotate) && !(log->flags & LOG_FLAG_SHAREDSCRIPTS)) + || (hasErrors && (log->flags & LOG_FLAG_SHAREDSCRIPTS)) + )) + ) { + if (!numRotated) { + message(MESS_DEBUG, "not running prerotate script, " + "since no logs will be rotated\n"); + } else { + message(MESS_DEBUG, "running prerotate script\n"); + if (runScript(log, log->flags & LOG_FLAG_SHAREDSCRIPTS ? log->pattern : log->files[j], log->pre)) { + if (log->flags & LOG_FLAG_SHAREDSCRIPTS) + message(MESS_ERROR, + "error running shared prerotate script " + "for '%s'\n", log->pattern); + else { + message(MESS_ERROR, + "error running non-shared prerotate script " + "for %s of '%s'\n", log->files[j], log->pattern); + } + logHasErrors[j] = 1; + hasErrors = 1; + } + } + } + + for (i = j; + ((log->flags & LOG_FLAG_SHAREDSCRIPTS) && i < log->numFiles) + || (!(log->flags & LOG_FLAG_SHAREDSCRIPTS) && i == j); i++) { + if (! ( (logHasErrors[i] && !(log->flags & LOG_FLAG_SHAREDSCRIPTS)) + || (hasErrors && (log->flags & LOG_FLAG_SHAREDSCRIPTS)) ) ) { + logHasErrors[i] |= + rotateSingleLog(log, i, state[i], rotNames[i]); + hasErrors |= logHasErrors[i]; + } + } + + if (log->post + && (!( + ((logHasErrors[j] || !state[j]->doRotate) && !(log->flags & LOG_FLAG_SHAREDSCRIPTS)) + || (hasErrors && (log->flags & LOG_FLAG_SHAREDSCRIPTS)) + )) + ) { + if (!numRotated) { + message(MESS_DEBUG, "not running postrotate script, " + "since no logs were rotated\n"); + } else { + message(MESS_DEBUG, "running postrotate script\n"); + if (runScript(log, log->flags & LOG_FLAG_SHAREDSCRIPTS ? log->pattern : log->files[j], log->post)) { + if (log->flags & LOG_FLAG_SHAREDSCRIPTS) + message(MESS_ERROR, + "error running shared postrotate script " + "for '%s'\n", log->pattern); + else { + message(MESS_ERROR, + "error running non-shared postrotate script " + "for %s of '%s'\n", log->files[j], log->pattern); + } + logHasErrors[j] = 1; + hasErrors = 1; + } + } + } + + for (i = j; + ((log->flags & LOG_FLAG_SHAREDSCRIPTS) && i < log->numFiles) + || (!(log->flags & LOG_FLAG_SHAREDSCRIPTS) && i == j); i++) { + if (! ( (logHasErrors[i] && !(log->flags & LOG_FLAG_SHAREDSCRIPTS)) + || (hasErrors && (log->flags & LOG_FLAG_SHAREDSCRIPTS)) ) ) { + logHasErrors[i] |= + postrotateSingleLog(log, i, state[i], rotNames[i]); + hasErrors |= logHasErrors[i]; + } + } + + } + + for (i = 0; i < log->numFiles; i++) { + free(rotNames[i]->firstRotated); + free(rotNames[i]->disposeName); + free(rotNames[i]->finalName); + free(rotNames[i]->dirName); + free(rotNames[i]->baseName); + free(rotNames[i]); + } + free(rotNames); + free(state); + + if (log->last) { + if (!numRotated) { + message(MESS_DEBUG, "not running last action script, " + "since no logs will be rotated\n"); + } else { + message(MESS_DEBUG, "running last action script\n"); + if (runScript(log, log->pattern, log->last)) { + message(MESS_ERROR, "error running last action script " + "for %s\n", log->pattern); + hasErrors = 1; + } + } + } + + if (log->flags & LOG_FLAG_SU) { + if (switch_user_back() != 0) { + return 1; + } + } + + return hasErrors; +} + +static int writeState(char *stateFilename) +{ + struct logState *p; + FILE *f; + char *chptr; + int i; + int error = 0; + int bytes = 0; + int fdcurr; + int fdsave; + struct stat sb; + char *tmpFilename = NULL; + struct tm now = *localtime(&nowSecs); + time_t now_time, last_time; + void *prevCtx; + + tmpFilename = malloc(strlen(stateFilename) + 5 ); + if (tmpFilename == NULL) { + message(MESS_ERROR, "could not allocate memory for " + "tmp state filename\n"); + return 1; + } + strcpy(tmpFilename, stateFilename); + strcat(tmpFilename, ".tmp"); + /* Remove possible tmp state file from previous run */ + unlink(tmpFilename); + + + if ((fdcurr = open(stateFilename, O_RDONLY)) < 0) { + message(MESS_ERROR, "error opening %s: %s\n", stateFilename, + strerror(errno)); + free(tmpFilename); + return 1; + } + + if (setSecCtx(fdcurr, stateFilename, &prevCtx) != 0) { + /* error msg already printed */ + close(fdcurr); + return 1; + } + +#ifdef WITH_ACL + if ((prev_acl = acl_get_fd(fdcurr)) == NULL) { + if (!ACL_NOT_WELL_SUPPORTED(errno)) { + message(MESS_ERROR, "getting file ACL %s: %s\n", + stateFilename, strerror(errno)); + restoreSecCtx(&prevCtx); + close(fdcurr); + return 1; + } + } +#endif + + close(fdcurr); + stat(stateFilename, &sb); + + fdsave = createOutputFile(tmpFilename, O_RDWR | O_CREAT | O_TRUNC, &sb, prev_acl, 0); +#ifdef WITH_ACL + if (prev_acl) { + acl_free(prev_acl); + prev_acl = NULL; + } +#endif + restoreSecCtx(&prevCtx); + + if (fdsave < 0) { + free(tmpFilename); + return 1; + } + + f = fdopen(fdsave, "w"); + if (!f) { + message(MESS_ERROR, "error creating temp state file %s: %s\n", + tmpFilename, strerror(errno)); + free(tmpFilename); + return 1; + } + + bytes = fprintf(f, "logrotate state -- version 2\n"); + if (bytes < 0) + error = bytes; + +#define SECONDS_IN_YEAR 31556926 + + for (i = 0; i < hashSize && error == 0; i++) { + for (p = states[i]->head.lh_first; p != NULL && error == 0; + p = p->list.le_next) { + + /* Skip states which are not used for more than a year. */ + now_time = mktime(&now); + last_time = mktime(&p->lastRotated); + if (!p->isUsed && difftime(now_time, last_time) > SECONDS_IN_YEAR) { + message(MESS_DEBUG, "Removing %s from state file, " + "because it does not exist and has not been rotated for one year\n", + p->fn); + continue; + } + + error = fputc('"', f) == EOF; + for (chptr = p->fn; *chptr && error == 0; chptr++) { + switch (*chptr) { + case '"': + case '\\': + error = fputc('\\', f) == EOF; + break; + case '\n': + error = fputc('\\', f) == EOF; + if (error == 0) { + error = fputc('n', f) == EOF; + } + continue; + } + if (error == 0 && fputc(*chptr, f) == EOF) { + error = 1; + } + } + + if (error == 0 && fputc('"', f) == EOF) + error = 1; + + if (error == 0) { + bytes = fprintf(f, " %d-%d-%d-%d:%d:%d\n", + p->lastRotated.tm_year + 1900, + p->lastRotated.tm_mon + 1, + p->lastRotated.tm_mday, + p->lastRotated.tm_hour, + p->lastRotated.tm_min, + p->lastRotated.tm_sec); + if (bytes < 0) + error = bytes; + } + } + } + + if (error == 0) + error = fsync(fdsave); + + if (error == 0) + error = fclose(f); + else + fclose(f); + + if (error == 0) { + if (rename(tmpFilename, stateFilename)) { + unlink(tmpFilename); + error = 1; + message(MESS_ERROR, "error renaming temp state file %s\n", + tmpFilename); + } + } + else { + unlink(tmpFilename); + if (errno) + message(MESS_ERROR, "error creating temp state file %s: %s\n", + tmpFilename, strerror(errno)); + else + message(MESS_ERROR, "error creating temp state file %s%s\n", + tmpFilename, error == ENOMEM ? + ": Insufficient storage space is available." : "" ); + } + free(tmpFilename); + return error; +} + +static int readState(char *stateFilename) +{ + FILE *f; + char buf[STATEFILE_BUFFER_SIZE]; + char *filename; + const char **argv; + int argc; + int year, month, day, hour, minute, second; + int i; + int line = 0; + int error; + struct logState *st; + time_t lr_time; + struct stat f_stat; + int rc = 0; + + message(MESS_DEBUG, "Reading state from file: %s\n", stateFilename); + + error = stat(stateFilename, &f_stat); + if (error) { + /* treat non-statable file as an empty file */ + f_stat.st_size = 0; + if (errno != ENOENT) { + message(MESS_ERROR, "error stat()ing state file %s: %s\n", + stateFilename, strerror(errno)); + + /* do not return until the hash table is allocated */ + rc = 1; + } + } + + if (!debug && (f_stat.st_size == 0)) { + /* create the file before continuing to ensure we have write + access to the file */ + f = fopen(stateFilename, "w"); + if (f) { + fprintf(f, "logrotate state -- version 2\n"); + fclose(f); + } else { + message(MESS_ERROR, "error creating state file %s: %s\n", + stateFilename, strerror(errno)); + + /* do not return until the hash table is allocated */ + rc = 1; + } + } + + /* Try to estimate how many state entries we have in the state file. + * We expect single entry to have around 80 characters (Of course this is + * just an estimation). During the testing I've found out that 200 entries + * per single hash entry gives good mem/performance ratio. */ + if (allocateHash(f_stat.st_size / 80 / 200)) + return 1; + + if (rc || (f_stat.st_size == 0)) + /* error already occurred, or we have no state file to read from */ + return rc; + + f = fopen(stateFilename, "r"); + if (!f) { + message(MESS_ERROR, "error opening state file %s: %s\n", + stateFilename, strerror(errno)); + return 1; + } + + if (!fgets(buf, sizeof(buf) - 1, f)) { + message(MESS_ERROR, "error reading top line of %s\n", + stateFilename); + fclose(f); + return 1; + } + + if (strcmp(buf, "logrotate state -- version 1\n") && + strcmp(buf, "logrotate state -- version 2\n")) { + fclose(f); + message(MESS_ERROR, "bad top line in state file %s\n", + stateFilename); + return 1; + } + + line++; + + while (fgets(buf, sizeof(buf) - 1, f)) { + argv = NULL; + line++; + i = strlen(buf); + if (buf[i - 1] != '\n') { + message(MESS_ERROR, "line %d too long in state file %s\n", + line, stateFilename); + fclose(f); + return 1; + } + + buf[i - 1] = '\0'; + + if (i == 1) + continue; + + year = month = day = hour = minute = second = 0; + if (poptParseArgvString(buf, &argc, &argv) || (argc != 2) || + (sscanf(argv[1], "%d-%d-%d-%d:%d:%d", &year, &month, &day, &hour, &minute, &second) < 3)) { + message(MESS_ERROR, "bad line %d in state file %s\n", + line, stateFilename); + free(argv); + fclose(f); + return 1; + } + + /* Hack to hide earlier bug */ + if ((year != 1900) && (year < 1970 || year > 2100)) { + message(MESS_ERROR, + "bad year %d for file %s in state file %s\n", year, + argv[0], stateFilename); + free(argv); + fclose(f); + return 1; + } + + if (month < 1 || month > 12) { + message(MESS_ERROR, + "bad month %d for file %s in state file %s\n", month, + argv[0], stateFilename); + free(argv); + fclose(f); + return 1; + } + + /* 0 to hide earlier bug */ + if (day < 0 || day > 31) { + message(MESS_ERROR, + "bad day %d for file %s in state file %s\n", day, + argv[0], stateFilename); + free(argv); + fclose(f); + return 1; + } + + if (hour < 0 || hour > 23) { + message(MESS_ERROR, + "bad hour %d for file %s in state file %s\n", hour, + argv[0], stateFilename); + free(argv); + fclose(f); + return 1; + } + + if (minute < 0 || minute > 59) { + message(MESS_ERROR, + "bad minute %d for file %s in state file %s\n", minute, + argv[0], stateFilename); + free(argv); + fclose(f); + return 1; + } + + if (second < 0 || second > 59) { + message(MESS_ERROR, + "bad second %d for file %s in state file %s\n", second, + argv[0], stateFilename); + free(argv); + fclose(f); + return 1; + } + + year -= 1900, month -= 1; + + filename = strdup(argv[0]); + unescape(filename); + + if ((st = findState(filename)) == NULL) { + fclose(f); + return 1; + } + + memset(&st->lastRotated, 0, sizeof(st->lastRotated)); + st->lastRotated.tm_year = year; + st->lastRotated.tm_mon = month; + st->lastRotated.tm_mday = day; + st->lastRotated.tm_hour = hour; + st->lastRotated.tm_min = minute; + st->lastRotated.tm_sec = second; + st->lastRotated.tm_isdst = -1; + + /* fill in the rest of the st->lastRotated fields */ + lr_time = mktime(&st->lastRotated); + st->lastRotated = *localtime(&lr_time); + + free(argv); + free(filename); + } + + fclose(f); + return 0; +} + +int main(int argc, const char **argv) +{ + int force = 0; + char *stateFile = STATEFILE; + char *logFile = NULL; + FILE *logFd = 0; + int rc = 0; + int arg; + const char **files; + poptContext optCon; + struct logInfo *log; + + struct poptOption options[] = { + {"debug", 'd', 0, 0, 'd', + "Don't do anything, just test (implies -v)"}, + {"force", 'f', 0, &force, 0, "Force file rotation"}, + {"mail", 'm', POPT_ARG_STRING, &mailCommand, 0, + "Command to send mail (instead of `" DEFAULT_MAIL_COMMAND "')", + "command"}, + {"state", 's', POPT_ARG_STRING, &stateFile, 0, + "Path of state file", + "statefile"}, + {"verbose", 'v', 0, 0, 'v', "Display messages during rotation"}, + {"log", 'l', POPT_ARG_STRING, &logFile, 'l', "Log file or 'syslog' to log to syslog"}, + {"version", '\0', POPT_ARG_NONE, NULL, 'V', "Display version information"}, + POPT_AUTOHELP {0, 0, 0, 0, 0} + }; + + logSetLevel(MESS_NORMAL); + setlocale (LC_ALL, ""); + + optCon = poptGetContext("logrotate", argc, argv, options, 0); + poptReadDefaultConfig(optCon, 1); + poptSetOtherOptionHelp(optCon, "[OPTION...] <configfile>"); + + while ((arg = poptGetNextOpt(optCon)) >= 0) { + switch (arg) { + case 'd': + debug = 1; + /* fallthrough */ + case 'v': + logSetLevel(MESS_DEBUG); + break; + case 'l': + if (strcmp(logFile, "syslog") == 0) { + logToSyslog(1); + } + else { + logFd = fopen(logFile, "w"); + if (!logFd) { + message(MESS_ERROR, "error opening log file %s: %s\n", + logFile, strerror(errno)); + break; + } + logSetMessageFile(logFd); + } + break; + case 'V': + fprintf(stderr, "logrotate %s\n", VERSION); + poptFreeContext(optCon); + exit(0); + } + } + + if (arg < -1) { + fprintf(stderr, "logrotate: bad argument %s: %s\n", + poptBadOption(optCon, POPT_BADOPTION_NOALIAS), + poptStrerror(rc)); + poptFreeContext(optCon); + return 2; + } + + files = poptGetArgs((poptContext) optCon); + if (!files) { + fprintf(stderr, "logrotate " VERSION + " - Copyright (C) 1995-2001 Red Hat, Inc.\n"); + fprintf(stderr, + "This may be freely redistributed under the terms of " + "the GNU Public License\n\n"); + poptPrintUsage(optCon, stderr, 0); + poptFreeContext(optCon); + exit(1); + } +#ifdef WITH_SELINUX + selinux_enabled = (is_selinux_enabled() > 0); + selinux_enforce = security_getenforce(); +#endif + + TAILQ_INIT(&logs); + + if (readAllConfigPaths(files)) { + poptFreeContext(optCon); + exit(1); + } + + poptFreeContext(optCon); + nowSecs = time(NULL); + + if (readState(stateFile)) + rc = 1; + + message(MESS_DEBUG, "\nHandling %d logs\n", numLogs); + + for (log = logs.tqh_first; log != NULL; log = log->list.tqe_next) + rc |= rotateLogSet(log, force); + + if (!debug) + rc |= writeState(stateFile); + + return (rc != 0); +}
diff --git a/logrotate/logrotate.conf b/logrotate/logrotate.conf new file mode 100644 index 0000000..f0d8cb6 --- /dev/null +++ b/logrotate/logrotate.conf
@@ -0,0 +1,12 @@ +compress + +/data/misc/logd/messages { +rotate 10 +size 1M +notifempty +missingok +copytruncate +lastaction + /system/bin/log_sender +endscript +}
diff --git a/logrotate/logrotate.conf.5 b/logrotate/logrotate.conf.5 new file mode 100644 index 0000000..7460664 --- /dev/null +++ b/logrotate/logrotate.conf.5
@@ -0,0 +1 @@ +.so man8/logrotate.8
diff --git a/logrotate/logrotate.h b/logrotate/logrotate.h new file mode 100644 index 0000000..81c4911 --- /dev/null +++ b/logrotate/logrotate.h
@@ -0,0 +1,89 @@ +#ifndef H_LOGROTATE +#define H_LOGROTATE + +#include <sys/types.h> +#include "queue.h" +#include <glob.h> + +#include "config.h" + +#define LOG_FLAG_COMPRESS (1 << 0) +#define LOG_FLAG_CREATE (1 << 1) +#define LOG_FLAG_IFEMPTY (1 << 2) +#define LOG_FLAG_DELAYCOMPRESS (1 << 3) +#define LOG_FLAG_COPYTRUNCATE (1 << 4) +#define LOG_FLAG_MISSINGOK (1 << 5) +#define LOG_FLAG_MAILFIRST (1 << 6) +#define LOG_FLAG_SHAREDSCRIPTS (1 << 7) +#define LOG_FLAG_COPY (1 << 8) +#define LOG_FLAG_DATEEXT (1 << 9) +#define LOG_FLAG_SHRED (1 << 10) +#define LOG_FLAG_SU (1 << 11) +#define LOG_FLAG_DATEYESTERDAY (1 << 12) +#define LOG_FLAG_OLDDIRCREATE (1 << 13) +#define LOG_FLAG_TMPFILENAME (1 << 14) + +#define NO_MODE ((mode_t) -1) +#define NO_UID ((uid_t) -1) +#define NO_GID ((gid_t) -1) + +#define NO_FORCE_ROTATE 0 +#define FORCE_ROTATE 1 + +#ifdef HAVE_LIBSELINUX +#define WITH_SELINUX 1 +#endif + +#ifdef HAVE_LIBACL +#define WITH_ACL 1 +#endif + +struct logInfo { + char *pattern; + char **files; + int numFiles; + char *oldDir; + enum { ROT_HOURLY, ROT_DAYS, ROT_WEEKLY, ROT_MONTHLY, ROT_YEARLY, ROT_SIZE + } criterium; + unsigned long long threshhold; + unsigned long long maxsize; + unsigned long long minsize; + int rotateCount; + int rotateMinAge; + int rotateAge; + int logStart; + char *pre, *post, *first, *last, *preremove; + char *logAddress; + char *extension; + char *addextension; + char *compress_prog; + char *uncompress_prog; + char *compress_ext; + char *dateformat; /* specify format for strftime (for dateext) */ + int flags; + int shred_cycles; /* if !=0, pass -n shred_cycles to GNU shred */ + mode_t createMode; /* if any/all of these are -1, we use the */ + uid_t createUid; /* attributes from the log file just rotated */ + gid_t createGid; + uid_t suUid; /* switch user to this uid and group to this gid */ + gid_t suGid; + mode_t olddirMode; + uid_t olddirUid; + uid_t olddirGid; + /* these are at the end so they end up nil */ + const char **compress_options_list; + int compress_options_count; + TAILQ_ENTRY(logInfo) list; +}; + +TAILQ_HEAD(logInfoHead, logInfo) logs; + +extern int numLogs; +extern int debug; + +int readAllConfigPaths(const char **paths); +#if !defined(asprintf) && !defined(_FORTIFY_SOURCE) +int asprintf(char **string_ptr, const char *format, ...); +#endif + +#endif
diff --git a/logrotate/logrotate.rc b/logrotate/logrotate.rc new file mode 100644 index 0000000..e7d7b3e --- /dev/null +++ b/logrotate/logrotate.rc
@@ -0,0 +1,14 @@ +on post-fs-data + mkdir /data/misc/logrotate 0770 log log + mkdir /data/misc/logrotate/lock 0770 log log + mkdir /data/misc/logrotate/run 0770 log log + mkdir /data/misc/logrotate/tmp 0770 log log + +# run each 1 min with 30 sec timeout for sending logs (logrotate will be killed after timeout) +service logrotate /system/bin/periodic_scheduler 60 30 logrotate \ + /data/misc/logrotate/cron-lite/ \ + /system/bin/logrotate -v /system/etc/logrotate.conf + class late_start + user log + group log system inet + seclabel u:r:logrotate:s0
diff --git a/logrotate/logrotate.spec.in b/logrotate/logrotate.spec.in new file mode 100644 index 0000000..aef7806 --- /dev/null +++ b/logrotate/logrotate.spec.in
@@ -0,0 +1,56 @@ +Summary: Rotates, compresses, removes and mails system log files +Name: logrotate +Version: @VERSION@ +Release: 1%{?dist} +License: GPLv2+ +Group: System Environment/Base +Source: https://github.com/logrotate/logrotate/releases/download/%{version}/logrotate-%{version}.tar.gz +Requires: coreutils >= 5.92 libsepol libselinux popt +BuildRequires: libselinux-devel popt-devel +BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) + +%{!?_licensedir:%global license %%doc} + +%description +The logrotate utility is designed to simplify the administration of +log files on a system which generates a lot of log files. Logrotate +allows for the automatic rotation compression, removal and mailing of +log files. Logrotate can be set to handle a log file daily, weekly, +monthly or when the log file gets to a certain size. Normally, +logrotate runs as a daily cron job. + +Install the logrotate package if you need a utility to deal with the +log files on your system. + +%prep +%setup -q + +%build +%configure \ + --with-selinux +make %{?_smp_mflags} RPM_OPT_FLAGS="$RPM_OPT_FLAGS" + +%install +rm -rf $RPM_BUILD_ROOT +make install DESTDIR=$RPM_BUILD_ROOT MANDIR=%{_mandir} +mkdir -p $RPM_BUILD_ROOT/%{_sysconfdir}/logrotate.d +mkdir -p $RPM_BUILD_ROOT/%{_sysconfdir}/cron.daily +mkdir -p $RPM_BUILD_ROOT/%{_localstatedir}/lib + +install -p -m 644 examples/logrotate-default $RPM_BUILD_ROOT/%{_sysconfdir}/logrotate.conf +install -p -m 755 examples/logrotate.cron $RPM_BUILD_ROOT/%{_sysconfdir}/cron.daily/logrotate +touch $RPM_BUILD_ROOT/%{_localstatedir}/lib/logrotate.status + +%clean +rm -rf $RPM_BUILD_ROOT + +%files +%defattr(-,root,root) +%license COPYING +%attr(0755, root, root) %{_sbindir}/logrotate +%attr(0644, root, root) %{_mandir}/man8/logrotate.8* +%attr(0644, root, root) %{_mandir}/man5/logrotate.conf.5* +%attr(0755, root, root) %{_sysconfdir}/cron.daily/logrotate +%attr(0644, root, root) %config(noreplace) %{_sysconfdir}/logrotate.conf +%attr(0755, root, root) %dir %{_sysconfdir}/logrotate.d +%attr(0644, root, root) %verify(not size md5 mtime) %config(noreplace) %{_localstatedir}/lib/logrotate.status
diff --git a/logrotate/queue.h b/logrotate/queue.h new file mode 100644 index 0000000..daf4553 --- /dev/null +++ b/logrotate/queue.h
@@ -0,0 +1,574 @@ +/* + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)queue.h 8.5 (Berkeley) 8/20/94 + */ + +#ifndef _SYS_QUEUE_H_ +#define _SYS_QUEUE_H_ + +/* + * This file defines five types of data structures: singly-linked lists, + * lists, simple queues, tail queues, and circular queues. + * + * A singly-linked list is headed by a single forward pointer. The + * elements are singly linked for minimum space and pointer manipulation + * overhead at the expense of O(n) removal for arbitrary elements. New + * elements can be added to the list after an existing element or at the + * head of the list. Elements being removed from the head of the list + * should use the explicit macro for this purpose for optimum + * efficiency. A singly-linked list may only be traversed in the forward + * direction. Singly-linked lists are ideal for applications with large + * datasets and few or no removals or for implementing a LIFO queue. + * + * A list is headed by a single forward pointer (or an array of forward + * pointers for a hash table header). The elements are doubly linked + * so that an arbitrary element can be removed without a need to + * traverse the list. New elements can be added to the list before + * or after an existing element or at the head of the list. A list + * may only be traversed in the forward direction. + * + * A simple queue is headed by a pair of pointers, one the head of the + * list and the other to the tail of the list. The elements are singly + * linked to save space, so elements can only be removed from the + * head of the list. New elements can be added to the list after + * an existing element, at the head of the list, or at the end of the + * list. A simple queue may only be traversed in the forward direction. + * + * A tail queue is headed by a pair of pointers, one to the head of the + * list and the other to the tail of the list. The elements are doubly + * linked so that an arbitrary element can be removed without a need to + * traverse the list. New elements can be added to the list before or + * after an existing element, at the head of the list, or at the end of + * the list. A tail queue may be traversed in either direction. + * + * A circle queue is headed by a pair of pointers, one to the head of the + * list and the other to the tail of the list. The elements are doubly + * linked so that an arbitrary element can be removed without a need to + * traverse the list. New elements can be added to the list before or after + * an existing element, at the head of the list, or at the end of the list. + * A circle queue may be traversed in either direction, but has a more + * complex end of list detection. + * + * For details on the use of these macros, see the queue(3) manual page. + */ + +/* + * List definitions. + */ +#define LIST_HEAD(name, type) \ +struct name { \ + struct type *lh_first; /* first element */ \ +} + +#define LIST_HEAD_INITIALIZER(head) \ + { NULL } + +#define LIST_ENTRY(type) \ +struct { \ + struct type *le_next; /* next element */ \ + struct type **le_prev; /* address of previous next element */ \ +} + +/* + * List functions. + */ +#define LIST_INIT(head) do { \ + (head)->lh_first = NULL; \ +} while (/*CONSTCOND*/0) + +#define LIST_INSERT_AFTER(listelm, elm, field) do { \ + if (((elm)->field.le_next = (listelm)->field.le_next) != NULL) \ + (listelm)->field.le_next->field.le_prev = \ + &(elm)->field.le_next; \ + (listelm)->field.le_next = (elm); \ + (elm)->field.le_prev = &(listelm)->field.le_next; \ +} while (/*CONSTCOND*/0) + +#define LIST_INSERT_BEFORE(listelm, elm, field) do { \ + (elm)->field.le_prev = (listelm)->field.le_prev; \ + (elm)->field.le_next = (listelm); \ + *(listelm)->field.le_prev = (elm); \ + (listelm)->field.le_prev = &(elm)->field.le_next; \ +} while (/*CONSTCOND*/0) + +#define LIST_INSERT_HEAD(head, elm, field) do { \ + if (((elm)->field.le_next = (head)->lh_first) != NULL) \ + (head)->lh_first->field.le_prev = &(elm)->field.le_next;\ + (head)->lh_first = (elm); \ + (elm)->field.le_prev = &(head)->lh_first; \ +} while (/*CONSTCOND*/0) + +#define LIST_REMOVE(elm, field) do { \ + if ((elm)->field.le_next != NULL) \ + (elm)->field.le_next->field.le_prev = \ + (elm)->field.le_prev; \ + *(elm)->field.le_prev = (elm)->field.le_next; \ +} while (/*CONSTCOND*/0) + +#define LIST_FOREACH(var, head, field) \ + for ((var) = ((head)->lh_first); \ + (var); \ + (var) = ((var)->field.le_next)) + +/* + * List access methods. + */ +#define LIST_EMPTY(head) ((head)->lh_first == NULL) +#define LIST_FIRST(head) ((head)->lh_first) +#define LIST_NEXT(elm, field) ((elm)->field.le_next) + + +/* + * Singly-linked List definitions. + */ +#define SLIST_HEAD(name, type) \ +struct name { \ + struct type *slh_first; /* first element */ \ +} + +#define SLIST_HEAD_INITIALIZER(head) \ + { NULL } + +#define SLIST_ENTRY(type) \ +struct { \ + struct type *sle_next; /* next element */ \ +} + +/* + * Singly-linked List functions. + */ +#define SLIST_INIT(head) do { \ + (head)->slh_first = NULL; \ +} while (/*CONSTCOND*/0) + +#define SLIST_INSERT_AFTER(slistelm, elm, field) do { \ + (elm)->field.sle_next = (slistelm)->field.sle_next; \ + (slistelm)->field.sle_next = (elm); \ +} while (/*CONSTCOND*/0) + +#define SLIST_INSERT_HEAD(head, elm, field) do { \ + (elm)->field.sle_next = (head)->slh_first; \ + (head)->slh_first = (elm); \ +} while (/*CONSTCOND*/0) + +#define SLIST_REMOVE_HEAD(head, field) do { \ + (head)->slh_first = (head)->slh_first->field.sle_next; \ +} while (/*CONSTCOND*/0) + +#define SLIST_REMOVE(head, elm, type, field) do { \ + if ((head)->slh_first == (elm)) { \ + SLIST_REMOVE_HEAD((head), field); \ + } \ + else { \ + struct type *curelm = (head)->slh_first; \ + while(curelm->field.sle_next != (elm)) \ + curelm = curelm->field.sle_next; \ + curelm->field.sle_next = \ + curelm->field.sle_next->field.sle_next; \ + } \ +} while (/*CONSTCOND*/0) + +#define SLIST_FOREACH(var, head, field) \ + for((var) = (head)->slh_first; (var); (var) = (var)->field.sle_next) + +/* + * Singly-linked List access methods. + */ +#define SLIST_EMPTY(head) ((head)->slh_first == NULL) +#define SLIST_FIRST(head) ((head)->slh_first) +#define SLIST_NEXT(elm, field) ((elm)->field.sle_next) + + +/* + * Singly-linked Tail queue declarations. + */ +#define STAILQ_HEAD(name, type) \ +struct name { \ + struct type *stqh_first; /* first element */ \ + struct type **stqh_last; /* addr of last next element */ \ +} + +#define STAILQ_HEAD_INITIALIZER(head) \ + { NULL, &(head).stqh_first } + +#define STAILQ_ENTRY(type) \ +struct { \ + struct type *stqe_next; /* next element */ \ +} + +/* + * Singly-linked Tail queue functions. + */ +#define STAILQ_INIT(head) do { \ + (head)->stqh_first = NULL; \ + (head)->stqh_last = &(head)->stqh_first; \ +} while (/*CONSTCOND*/0) + +#define STAILQ_INSERT_HEAD(head, elm, field) do { \ + if (((elm)->field.stqe_next = (head)->stqh_first) == NULL) \ + (head)->stqh_last = &(elm)->field.stqe_next; \ + (head)->stqh_first = (elm); \ +} while (/*CONSTCOND*/0) + +#define STAILQ_INSERT_TAIL(head, elm, field) do { \ + (elm)->field.stqe_next = NULL; \ + *(head)->stqh_last = (elm); \ + (head)->stqh_last = &(elm)->field.stqe_next; \ +} while (/*CONSTCOND*/0) + +#define STAILQ_INSERT_AFTER(head, listelm, elm, field) do { \ + if (((elm)->field.stqe_next = (listelm)->field.stqe_next) == NULL)\ + (head)->stqh_last = &(elm)->field.stqe_next; \ + (listelm)->field.stqe_next = (elm); \ +} while (/*CONSTCOND*/0) + +#define STAILQ_REMOVE_HEAD(head, field) do { \ + if (((head)->stqh_first = (head)->stqh_first->field.stqe_next) == NULL) \ + (head)->stqh_last = &(head)->stqh_first; \ +} while (/*CONSTCOND*/0) + +#define STAILQ_REMOVE(head, elm, type, field) do { \ + if ((head)->stqh_first == (elm)) { \ + STAILQ_REMOVE_HEAD((head), field); \ + } else { \ + struct type *curelm = (head)->stqh_first; \ + while (curelm->field.stqe_next != (elm)) \ + curelm = curelm->field.stqe_next; \ + if ((curelm->field.stqe_next = \ + curelm->field.stqe_next->field.stqe_next) == NULL) \ + (head)->stqh_last = &(curelm)->field.stqe_next; \ + } \ +} while (/*CONSTCOND*/0) + +#define STAILQ_FOREACH(var, head, field) \ + for ((var) = ((head)->stqh_first); \ + (var); \ + (var) = ((var)->field.stqe_next)) + +#define STAILQ_CONCAT(head1, head2) do { \ + if (!STAILQ_EMPTY((head2))) { \ + *(head1)->stqh_last = (head2)->stqh_first; \ + (head1)->stqh_last = (head2)->stqh_last; \ + STAILQ_INIT((head2)); \ + } \ +} while (/*CONSTCOND*/0) + +/* + * Singly-linked Tail queue access methods. + */ +#define STAILQ_EMPTY(head) ((head)->stqh_first == NULL) +#define STAILQ_FIRST(head) ((head)->stqh_first) +#define STAILQ_NEXT(elm, field) ((elm)->field.stqe_next) + + +/* + * Simple queue definitions. + */ +#define SIMPLEQ_HEAD(name, type) \ +struct name { \ + struct type *sqh_first; /* first element */ \ + struct type **sqh_last; /* addr of last next element */ \ +} + +#define SIMPLEQ_HEAD_INITIALIZER(head) \ + { NULL, &(head).sqh_first } + +#define SIMPLEQ_ENTRY(type) \ +struct { \ + struct type *sqe_next; /* next element */ \ +} + +/* + * Simple queue functions. + */ +#define SIMPLEQ_INIT(head) do { \ + (head)->sqh_first = NULL; \ + (head)->sqh_last = &(head)->sqh_first; \ +} while (/*CONSTCOND*/0) + +#define SIMPLEQ_INSERT_HEAD(head, elm, field) do { \ + if (((elm)->field.sqe_next = (head)->sqh_first) == NULL) \ + (head)->sqh_last = &(elm)->field.sqe_next; \ + (head)->sqh_first = (elm); \ +} while (/*CONSTCOND*/0) + +#define SIMPLEQ_INSERT_TAIL(head, elm, field) do { \ + (elm)->field.sqe_next = NULL; \ + *(head)->sqh_last = (elm); \ + (head)->sqh_last = &(elm)->field.sqe_next; \ +} while (/*CONSTCOND*/0) + +#define SIMPLEQ_INSERT_AFTER(head, listelm, elm, field) do { \ + if (((elm)->field.sqe_next = (listelm)->field.sqe_next) == NULL)\ + (head)->sqh_last = &(elm)->field.sqe_next; \ + (listelm)->field.sqe_next = (elm); \ +} while (/*CONSTCOND*/0) + +#define SIMPLEQ_REMOVE_HEAD(head, field) do { \ + if (((head)->sqh_first = (head)->sqh_first->field.sqe_next) == NULL) \ + (head)->sqh_last = &(head)->sqh_first; \ +} while (/*CONSTCOND*/0) + +#define SIMPLEQ_REMOVE(head, elm, type, field) do { \ + if ((head)->sqh_first == (elm)) { \ + SIMPLEQ_REMOVE_HEAD((head), field); \ + } else { \ + struct type *curelm = (head)->sqh_first; \ + while (curelm->field.sqe_next != (elm)) \ + curelm = curelm->field.sqe_next; \ + if ((curelm->field.sqe_next = \ + curelm->field.sqe_next->field.sqe_next) == NULL) \ + (head)->sqh_last = &(curelm)->field.sqe_next; \ + } \ +} while (/*CONSTCOND*/0) + +#define SIMPLEQ_FOREACH(var, head, field) \ + for ((var) = ((head)->sqh_first); \ + (var); \ + (var) = ((var)->field.sqe_next)) + +/* + * Simple queue access methods. + */ +#define SIMPLEQ_EMPTY(head) ((head)->sqh_first == NULL) +#define SIMPLEQ_FIRST(head) ((head)->sqh_first) +#define SIMPLEQ_NEXT(elm, field) ((elm)->field.sqe_next) + + +/* + * Tail queue definitions. + */ +#define _TAILQ_HEAD(name, type, qual) \ +struct name { \ + qual type *tqh_first; /* first element */ \ + qual type *qual *tqh_last; /* addr of last next element */ \ +} +#define TAILQ_HEAD(name, type) _TAILQ_HEAD(name, struct type,) + +#define TAILQ_HEAD_INITIALIZER(head) \ + { NULL, &(head).tqh_first } + +#define _TAILQ_ENTRY(type, qual) \ +struct { \ + qual type *tqe_next; /* next element */ \ + qual type *qual *tqe_prev; /* address of previous next element */\ +} +#define TAILQ_ENTRY(type) _TAILQ_ENTRY(struct type,) + +/* + * Tail queue functions. + */ +#define TAILQ_INIT(head) do { \ + (head)->tqh_first = NULL; \ + (head)->tqh_last = &(head)->tqh_first; \ +} while (/*CONSTCOND*/0) + +#define TAILQ_INSERT_HEAD(head, elm, field) do { \ + if (((elm)->field.tqe_next = (head)->tqh_first) != NULL) \ + (head)->tqh_first->field.tqe_prev = \ + &(elm)->field.tqe_next; \ + else \ + (head)->tqh_last = &(elm)->field.tqe_next; \ + (head)->tqh_first = (elm); \ + (elm)->field.tqe_prev = &(head)->tqh_first; \ +} while (/*CONSTCOND*/0) + +#define TAILQ_INSERT_TAIL(head, elm, field) do { \ + (elm)->field.tqe_next = NULL; \ + (elm)->field.tqe_prev = (head)->tqh_last; \ + *(head)->tqh_last = (elm); \ + (head)->tqh_last = &(elm)->field.tqe_next; \ +} while (/*CONSTCOND*/0) + +#define TAILQ_INSERT_AFTER(head, listelm, elm, field) do { \ + if (((elm)->field.tqe_next = (listelm)->field.tqe_next) != NULL)\ + (elm)->field.tqe_next->field.tqe_prev = \ + &(elm)->field.tqe_next; \ + else \ + (head)->tqh_last = &(elm)->field.tqe_next; \ + (listelm)->field.tqe_next = (elm); \ + (elm)->field.tqe_prev = &(listelm)->field.tqe_next; \ +} while (/*CONSTCOND*/0) + +#define TAILQ_INSERT_BEFORE(listelm, elm, field) do { \ + (elm)->field.tqe_prev = (listelm)->field.tqe_prev; \ + (elm)->field.tqe_next = (listelm); \ + *(listelm)->field.tqe_prev = (elm); \ + (listelm)->field.tqe_prev = &(elm)->field.tqe_next; \ +} while (/*CONSTCOND*/0) + +#define TAILQ_REMOVE(head, elm, field) do { \ + if (((elm)->field.tqe_next) != NULL) \ + (elm)->field.tqe_next->field.tqe_prev = \ + (elm)->field.tqe_prev; \ + else \ + (head)->tqh_last = (elm)->field.tqe_prev; \ + *(elm)->field.tqe_prev = (elm)->field.tqe_next; \ +} while (/*CONSTCOND*/0) + +#define TAILQ_FOREACH(var, head, field) \ + for ((var) = ((head)->tqh_first); \ + (var); \ + (var) = ((var)->field.tqe_next)) + +#define TAILQ_FOREACH_REVERSE(var, head, headname, field) \ + for ((var) = (*(((struct headname *)((head)->tqh_last))->tqh_last)); \ + (var); \ + (var) = (*(((struct headname *)((var)->field.tqe_prev))->tqh_last))) + +#define TAILQ_CONCAT(head1, head2, field) do { \ + if (!TAILQ_EMPTY(head2)) { \ + *(head1)->tqh_last = (head2)->tqh_first; \ + (head2)->tqh_first->field.tqe_prev = (head1)->tqh_last; \ + (head1)->tqh_last = (head2)->tqh_last; \ + TAILQ_INIT((head2)); \ + } \ +} while (/*CONSTCOND*/0) + +/* + * Tail queue access methods. + */ +#define TAILQ_EMPTY(head) ((head)->tqh_first == NULL) +#define TAILQ_FIRST(head) ((head)->tqh_first) +#define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next) + +#define TAILQ_LAST(head, headname) \ + (*(((struct headname *)((head)->tqh_last))->tqh_last)) +#define TAILQ_PREV(elm, headname, field) \ + (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last)) + + +/* + * Circular queue definitions. + */ +#define CIRCLEQ_HEAD(name, type) \ +struct name { \ + struct type *cqh_first; /* first element */ \ + struct type *cqh_last; /* last element */ \ +} + +#define CIRCLEQ_HEAD_INITIALIZER(head) \ + { (void *)&head, (void *)&head } + +#define CIRCLEQ_ENTRY(type) \ +struct { \ + struct type *cqe_next; /* next element */ \ + struct type *cqe_prev; /* previous element */ \ +} + +/* + * Circular queue functions. + */ +#define CIRCLEQ_INIT(head) do { \ + (head)->cqh_first = (void *)(head); \ + (head)->cqh_last = (void *)(head); \ +} while (/*CONSTCOND*/0) + +#define CIRCLEQ_INSERT_AFTER(head, listelm, elm, field) do { \ + (elm)->field.cqe_next = (listelm)->field.cqe_next; \ + (elm)->field.cqe_prev = (listelm); \ + if ((listelm)->field.cqe_next == (void *)(head)) \ + (head)->cqh_last = (elm); \ + else \ + (listelm)->field.cqe_next->field.cqe_prev = (elm); \ + (listelm)->field.cqe_next = (elm); \ +} while (/*CONSTCOND*/0) + +#define CIRCLEQ_INSERT_BEFORE(head, listelm, elm, field) do { \ + (elm)->field.cqe_next = (listelm); \ + (elm)->field.cqe_prev = (listelm)->field.cqe_prev; \ + if ((listelm)->field.cqe_prev == (void *)(head)) \ + (head)->cqh_first = (elm); \ + else \ + (listelm)->field.cqe_prev->field.cqe_next = (elm); \ + (listelm)->field.cqe_prev = (elm); \ +} while (/*CONSTCOND*/0) + +#define CIRCLEQ_INSERT_HEAD(head, elm, field) do { \ + (elm)->field.cqe_next = (head)->cqh_first; \ + (elm)->field.cqe_prev = (void *)(head); \ + if ((head)->cqh_last == (void *)(head)) \ + (head)->cqh_last = (elm); \ + else \ + (head)->cqh_first->field.cqe_prev = (elm); \ + (head)->cqh_first = (elm); \ +} while (/*CONSTCOND*/0) + +#define CIRCLEQ_INSERT_TAIL(head, elm, field) do { \ + (elm)->field.cqe_next = (void *)(head); \ + (elm)->field.cqe_prev = (head)->cqh_last; \ + if ((head)->cqh_first == (void *)(head)) \ + (head)->cqh_first = (elm); \ + else \ + (head)->cqh_last->field.cqe_next = (elm); \ + (head)->cqh_last = (elm); \ +} while (/*CONSTCOND*/0) + +#define CIRCLEQ_REMOVE(head, elm, field) do { \ + if ((elm)->field.cqe_next == (void *)(head)) \ + (head)->cqh_last = (elm)->field.cqe_prev; \ + else \ + (elm)->field.cqe_next->field.cqe_prev = \ + (elm)->field.cqe_prev; \ + if ((elm)->field.cqe_prev == (void *)(head)) \ + (head)->cqh_first = (elm)->field.cqe_next; \ + else \ + (elm)->field.cqe_prev->field.cqe_next = \ + (elm)->field.cqe_next; \ +} while (/*CONSTCOND*/0) + +#define CIRCLEQ_FOREACH(var, head, field) \ + for ((var) = ((head)->cqh_first); \ + (var) != (const void *)(head); \ + (var) = ((var)->field.cqe_next)) + +#define CIRCLEQ_FOREACH_REVERSE(var, head, field) \ + for ((var) = ((head)->cqh_last); \ + (var) != (const void *)(head); \ + (var) = ((var)->field.cqe_prev)) + +/* + * Circular queue access methods. + */ +#define CIRCLEQ_EMPTY(head) ((head)->cqh_first == (void *)(head)) +#define CIRCLEQ_FIRST(head) ((head)->cqh_first) +#define CIRCLEQ_LAST(head) ((head)->cqh_last) +#define CIRCLEQ_NEXT(elm, field) ((elm)->field.cqe_next) +#define CIRCLEQ_PREV(elm, field) ((elm)->field.cqe_prev) + +#define CIRCLEQ_LOOP_NEXT(head, elm, field) \ + (((elm)->field.cqe_next == (void *)(head)) \ + ? ((head)->cqh_first) \ + : (elm->field.cqe_next)) +#define CIRCLEQ_LOOP_PREV(head, elm, field) \ + (((elm)->field.cqe_prev == (void *)(head)) \ + ? ((head)->cqh_last) \ + : (elm->field.cqe_prev)) + +#endif /* sys/queue.h */
diff --git a/logrotate/test/Makefile.am b/logrotate/test/Makefile.am new file mode 100644 index 0000000..3842451 --- /dev/null +++ b/logrotate/test/Makefile.am
@@ -0,0 +1,17 @@ +EXTRA_DIST = \ + compress \ + compress-error \ + mailer \ + test \ + $(top_srcdir)/test/test-config.*.in + +DISTCLEANFILES = \ + test.ACL \ + test.SELINUX \ + test.example + +TESTS_ENVIRONMENT = \ + test $(top_srcdir) = $(top_builddir) || ln -fs $(top_srcdir)/test/* . ; \ + export LOGROTATE=$(top_builddir)/logrotate ; + +TESTS = test
diff --git a/logrotate/test/compress b/logrotate/test/compress new file mode 100755 index 0000000..0eb626d --- /dev/null +++ b/logrotate/test/compress
@@ -0,0 +1,5 @@ +#!/bin/bash + +echo "gzip $*" > compress-args +env > compress-env +gzip $* \ No newline at end of file
diff --git a/logrotate/test/compress-error b/logrotate/test/compress-error new file mode 100755 index 0000000..d0d09d9 --- /dev/null +++ b/logrotate/test/compress-error
@@ -0,0 +1,4 @@ +#!/bin/bash + +echo "compression error" 1>&2 +gzip $* \ No newline at end of file
diff --git a/logrotate/test/mailer b/logrotate/test/mailer new file mode 100755 index 0000000..73a72fa --- /dev/null +++ b/logrotate/test/mailer
@@ -0,0 +1,4 @@ +#!/bin/bash + +echo "$*" > mail-out +cat >> mail-out
diff --git a/logrotate/test/test b/logrotate/test/test new file mode 100755 index 0000000..1ac7a2e --- /dev/null +++ b/logrotate/test/test
@@ -0,0 +1,1867 @@ +#!/bin/bash + +ACL_TESTS=1 +SELINUX_TESTS=0 +SYSLOG_TESTS=0 +M="-m ./mailer" +S="-s state" +RLR="$LOGROTATE $M $S" + +# -- ACL - BEGIN -------------------------------- +# We test if the ACLs tests should be done or not +echo 1 > test.x +setfacl -m u:nobody:rwx test.x 2>/dev/null +if [ $? != 0 ]; then + ACL_TESTS=0 + echo "setfacl failed on this system. ACL tests will not be executed." +fi +rm -f test.x +if [ $ACL_TESTS = 1 ]; then + # It seems we can run the ACL tests, but was logrotate compiled WITH_ACL=yes ? + # See the Makefile, "pretest" part, for more information + if [ -f ./test.ACL ]; then + ACL_TESTS=`cat ./test.ACL` + if [ $ACL_TESTS = 0 ]; then + echo "logrotate was NOT compiled with 'WITH_ACL=yes'. ACL tests will not be executed." + fi + fi +fi +# -- ACL - END ---------------------------------- + +# -- SELINUX - BEGIN -------------------------------- +# We test if the ACLs tests should be done or not +if type "selinuxenabled" >/dev/null 2>&1 && selinuxenabled; then + SELINUX_TESTS=1 +else + echo "SELinux disabled. SELinux tests will not be executed." +fi + +if [ $SELINUX_TESTS = 1 ]; then + # It seems we can run the ACL tests, but was logrotate compiled WITH_ACL=yes ? + # See the Makefile, "pretest" part, for more information + if [ -f ./test.SELINUX ]; then + SELINUX_TESTS=`cat ./test.SELINUX` + if [ $SELINUX_TESTS = 0 ]; then + echo "logrotate was NOT compiled with 'WITH_SELINUX=yes'. SELINUX tests will not be executed." + fi + fi +fi + +if [ $SELINUX_TESTS = 1 ]; then + # if logrotate_tmp_t, we can't continue with SELinux tests... + touch .selinuxtest + chcon --type=logrotate_tmp_t .selinuxtest 2>/dev/null + if [ $? != 0 ]; then + SELINUX_TESTS=0 + echo "SELinux context 'logrotate_tmp_t' does not exist. SELinux tests will not be executed." + fi + rm -f .selinuxtest +fi +# -- SELINUX - END ---------------------------------- + +# -- SYSLOG - BEGIN +logger syslog_test 2>/dev/null +if [ $? = 0 ]; then + journalctl -n 50 2>/dev/null | grep syslog_test 2>/dev/null >/dev/null + if [ $? = 0 ]; then + SYSLOG_TESTS=1 + fi +fi +# -- SYSLOG - END + +cleanup() { + rm -f test*.log* anothertest*.log* state test-config. scriptout mail-out compress-args compress-env different*.log* + rm -f $(ls | egrep '^test-config.[0-9]+$') + + [ -n "$1" ] && echo "Running test $1" + return 0 +} + +genconfig() { + input=test-config.$1.in + output=test-config.$1 + user=$(id -u -n) + group=$(id -g -n) + sed "s,&DIR&,$PWD,g" < $input | sed "s,&USER&,$user,g" | sed "s,&GROUP&,$group,g" > $output + config_crc=$(md5sum $output) +} + +createlog() { + num=$1 + file=$2 + cl_compressed=$3 + + case $num in + 0) + what=zero + ;; + 1) + what=first + ;; + 2) + what=second + ;; + 3) + what=third + ;; + 4) + what=fourth + ;; + 5) + what=fifth + ;; + 6) + what=sixth + ;; + 7) + what=seventh + ;; + 8) + what=eight + ;; + 9) + what=ninth + ;; + *) + exit 1 + ;; + esac + + echo $what > $file + [ -n "$cl_compressed" ] && gzip -9 $file +} + +createlogs() { + base=$1 + numlogs=$2 + cls_compressed=$3 + + rm -f ${base}* + + num=0 + while [ $num != $numlogs ]; do + if [ $num = 0 ]; then + createlog 0 $base + else + createlog $num ${base}.$num $cls_compressed + fi + + num=`expr $num + 1` + done +} + +checkmail() { + (echo -s $PWD/$1 user@myhost.org; echo $2) | diff -u - mail-out + if [ $? != 0 ]; then + exit 5 + fi +} + +checkoutput() { + while read line; do + set $line + file=$1 + co_compressed=$2 + if [ "$#" -le 2 ]; then + shift $# + else + shift 2 + fi + + fileother=`echo $line | awk '{print $1}'` + expected=`echo $line | cut -s -d\ -f3-` + + if [ $file != $fileother ]; then + echo "unexpected file $file'" >&2 + exit 2 + fi + + if [ ! -f $file ]; then + echo "file $file does not exist" + fi + + if [ -n "$co_compressed" ] && [ "$co_compressed" != 0 ]; then + contents=`gunzip -c $file` + else + contents=`cat $file | tr -d '\000'` + fi + if [ "$contents" != "$expected" ]; then + echo "file $file does not contain expected results (compressed $co_compressed, args $*)" >&2 + echo contains: \'$contents\' + echo expected: \'$expected\' + exit 2 + fi + echo "$config_crc" | md5sum -c - 2>&1 > /dev/null + if [ $? != 0 ]; then + echo "config file $output has been altered: MD5 sum mismatch" + exit 3 + fi + done +} + +preptest() { + base=$1 + confignum=$2 + numlogs=$3 + pt_compressed=$4 + + rm -f $base* + rm -f state + + genconfig $confignum + createlogs $base $numlogs $pt_compressed +} + +# we don't want any stuff left from previous runs +cleanup 1 + +# ------------------------------- Test 1 ------------------------------------- +# Without a log file, no rotations should occur +preptest test.log 1 2 +$RLR test-config.1 + +checkoutput <<EOF +test.log 0 zero +test.log.1 0 first +EOF + +# Put in place a state file that will force a rotation +cat > state <<EOF +logrotate state -- version 1 +"$PWD/test.log" 2000-1-1 +EOF + +# Now force the rotation +$RLR test-config.1 +checkoutput <<EOF +test.log 0 +test.log.1 0 zero +test.log.2 0 first +EOF + +# rerun it to make sure nothing happens +$RLR test-config.1 + +checkoutput <<EOF +test.log +test.log.1 0 zero +test.log.2 0 first +EOF + +cleanup 2 + +# ------------------------------- Test 2 ------------------------------------- +preptest test.log 2 3 +$RLR test-config.2 --force + +checkoutput <<EOF +test.log.1 0 zero +test.log.2 0 first +EOF + +checkmail test.log.3 second + +if [ -f test.log ]; then + echo "erroneously created test.log" +fi + +cleanup 3 + +# ------------------------------- Test 3 ------------------------------------- + +preptest test.log 3 1 +$RLR test-config.3 --force + +checkoutput <<EOF +test.log 0 +test.log.1 0 zero +scriptout 0 foo +EOF + +cleanup + +preptest test.log 3 1 +preptest test2.log 3 1 +$RLR test-config.3 --force + +checkoutput <<EOF +test.log 0 +test.log.1 0 zero +test2.log 0 +test2.log.1 0 zero +scriptout 0 foo foo +EOF + +cleanup 4 + +# ------------------------------- Test 4 ------------------------------------- +preptest test.log 4 1 +preptest test2.log 4 1 +$RLR test-config.4 --force + +checkoutput <<EOF +test.log 0 +test.log.1 0 zero +test2.log 0 +test2.log.1 0 zero +scriptout 0 foo +EOF + +cleanup 5 + +# ------------------------------- Test 5 ------------------------------------- +preptest test.log 5 1 +preptest anothertest.log 5 1 +$RLR test-config.5 --force + +checkoutput <<EOF +test.log 0 +test.log.1 0 zero +anothertest.log 0 +anothertest.log.1 0 zero +scriptout 0 foo +EOF + +cleanup 6 + +# ------------------------------- Test 6 ------------------------------------- +preptest test.log 6 1 +preptest anothertest.log 6 1 +if [ $SELINUX_TESTS = 1 ]; then + chcon --type=logrotate_tmp_t test.log +else + echo "Skipping SELinux part of test 6" +fi +$RLR test-config.6 --force + +if [ $SELINUX_TESTS = 1 ]; then + ls -Z test.log.0|grep logrotate_tmp_t >/dev/null + if [ $? != 0 ]; then + echo "test.log.0 should have selinux context logrotate_tmp_t." + exit 3 + fi + + ls -Z anothertest.log.0|grep logrotate_tmp_t >/dev/null + if [ $? = 0 ]; then + echo "anothertest.log.0 should not have selinux context logrotate_tmp_t." + exit 3 + fi +fi + +checkoutput <<EOF +test.log 0 +test.log.0 0 zero +anothertest.log 0 +anothertest.log.0 0 zero +scriptout 0 foo +EOF + +cleanup 7 + +# ------------------------------- Test 7 ------------------------------------- +preptest test.log 7 1 +preptest anothertest.log 7 1 + +$RLR test-config.7 --force + +checkoutput <<EOF +test.log 0 +test.log.6 0 zero +anothertest.log 0 +anothertest.log.6 0 zero +scriptout 0 foo +EOF + +cleanup 8 + +# ------------------------------- Test 8 ------------------------------------- +preptest test.log 8 1 1 +$RLR test-config.8 --force + +checkoutput <<EOF +test.log 0 +test.log.1.gz 1 zero +scriptout 0 foo +EOF + +checkmail test.log zero + +cleanup 9 + +# ------------------------------- Test 9 ------------------------------------- +preptest test.log 9 1 1 +$RLR test-config.9 --force + +checkoutput <<EOF +test.log 0 +scriptout 0 foo +EOF + +checkmail test.log zero + +cleanup 10 + +# ------------------------------- Test 10 ------------------------------------ +preptest test.log 10 1 + +if [ $SELINUX_TESTS = 1 ]; then + chcon --type=logrotate_tmp_t test.log +else + echo "Skipping SELinux part of test 10" +fi + +$RLR test-config.10 --force + +checkoutput <<EOF +test.log 0 +test.log.1 0 zero +EOF + +echo "newfile" > test.log + +$RLR test-config.10 --force + +if [ $SELINUX_TESTS = 1 ]; then + ls -Z test.log.2.gz|grep logrotate_tmp_t >/dev/null + if [ $? != 0 ]; then + echo "test.log.2.gz should have selinux context logrotate_tmp_t." + ls -Z test.log.2.gz + exit 3 + fi + + ls -Z test.log.1|grep logrotate_tmp_t >/dev/null + if [ $? != 0 ]; then + echo "test.log.1 should have selinux context logrotate_tmp_t." + ls -Z test.log.1 + exit 3 + fi +fi + +checkoutput <<EOF +test.log 0 +test.log.1 0 newfile +test.log.2.gz 1 zero +EOF + +checkmail test.log.1 newfile + +cleanup 11 + +# ------------------------------- Test 11 ------------------------------------ +preptest test.log 11 2 1 +$RLR test-config.11 --force + +checkoutput <<EOF +test.log 0 +scriptout 0 foo +EOF + +checkmail test.log.2.gz first + +# check rotation into a directory given as a relative pathname +cleanup 12 + +# ------------------------------- Test 12 ------------------------------------ +preptest test.log 12 1 0 +rm -rf testdir +mkdir testdir +$RLR test-config.12 --force + +checkoutput <<EOF +test.log 0 +testdir/test.log.1 0 zero +EOF + +rm -rf testdir + +# check rotation into a directory given as an absolute pathname +cleanup 13 + +# ------------------------------- Test 13 ------------------------------------ +preptest test.log 13 1 0 +rm -rf testdir +$RLR test-config.13 --force + +ls -l|grep testdir|grep "drwx------." 2>/dev/null >/dev/null +if [ $? != 0 ]; then + echo "testdir should have mode 2700, but it has:" + ls -l|grep testdir + exit 3 +fi + +checkoutput <<EOF +test.log 0 +testdir/test.log.1 0 zero +EOF + +rm -rf testdir + +# sanity rotation check using dateext and dateformat +cleanup 14 + +# ------------------------------- Test 14 ------------------------------------ +preptest test.log 14 1 0 + +$RLR test-config.14 --force + +DATESTRING=$(/bin/date +%Y-%m-%d) + +checkoutput <<EOF +test.log 0 +test.log.$DATESTRING 0 zero +EOF + +rm -rf testdir + +# shred test +cleanup 15 + +# ------------------------------- Test 15 ------------------------------------ +preptest test.log 15 1 0 +$RLR test-config.15 --force + +# this rotation should use shred +$RLR test-config.15 --force + +checkoutput <<EOF +test.log 0 +test.log.1 0 +EOF + +cleanup 16 + +# ------------------------------- Test 16 ------------------------------------ +preptest test.log 16 1 0 +# log with 1 byte should not be rotated +echo "a" > test.log +$RLR test-config.16 + +if [ -f test.log.1 ]; then + echo "file $file does exist!" + exit 2 +fi + +# log with 4 bytes should be rotated +echo "zero" > test.log +$RLR test-config.16 + +checkoutput <<EOF +test.log 0 +test.log.1 0 zero +EOF + +cleanup 17 + +# ------------------------------- Test 17 ------------------------------------ +preptest test.log 17 1 0 +# log with 1 byte should not be rotated +$RLR test-config.17 -l logrotate.log 2>error.log + +grep "unexpected } (missing previous '{')" error.log >/dev/null +if [ $? != 0 ]; then + echo "No error printed, but there should be one." + exit 3 +fi + +rm error.log + +grep "reading config file test-config.17" logrotate.log >/dev/null +if [ $? != 0 ]; then + echo "There is no log output in logrotate.log" + exit 3 +fi + +rm -f logrotate.log + +checkoutput <<EOF +test.log 0 zero +EOF + +cleanup 18 + +# ------------------------------- Test 18 ------------------------------------ +preptest test.log 18 1 +$RLR test-config.18 -l syslog --force + +checkoutput <<EOF +test.log 0 +test.log.1.gz 1 zero +EOF + +(echo "gzip -f -9") | diff -u - compress-args +egrep -q '^LOGROTATE_COMPRESSED_FILENAME=.+/test/test.log.1$' compress-env +if [ $? != 0 ]; then + echo "LOGROTATE_COMPRESSED_FILENAME environment variable not found." + cat compress-env++ exit 3 +fi + +if [ $SYSLOG_TESTS = 1 ]; then + journalctl -n 50 2>/dev/null|grep $PWD/test.log.1 2>/dev/null >/dev/null + if [ $? != 0 ]; then + echo "syslog message not found" + exit 1 + fi +fi + +cleanup 19 + +# ------------------------------- Test 19 ------------------------------------ +preptest test.log 19 1 +$RLR test-config.19 --force 2>error.log +if [ $? = 0 ]; then + echo "Logrotate exited with 0 exit code, but it should not" +fi + +grep "error running non-shared postrotate script for" error.log >/dev/null +if [ $? != 0 ]; then + echo "No error printed, but there should be one." + exit 3 +fi + +cleanup 20 + +# ------------------------------- Test 20 ------------------------------------ +preptest test.log 20 1 +$RLR test-config.20 --force 2>error.log + +if [ $? = 0 ]; then + echo "Logrotate exited with 0 exit code, but it should not" +fi + +grep "error running shared postrotate script for" error.log >/dev/null +if [ $? != 0 ]; then + echo "No error printed, but there should be one." + exit 3 +fi + +cleanup 21 + +# ------------------------------- Test 21 ------------------------------------ +# different base name, so it should not find the file +preptest differenttest.log 21 1 +$RLR test-config.21 --force 2>error.log + +if [ $? != 0 ]; then + echo "Logrotate exited with non-zero exit code, but it should not" +fi + +cat error.log + +# grep "error running shared postrotate script for" error.log >/dev/null +# if [ $? != 0 ]; then +# echo "No error printed, but there should be one." +# exit 3 +# fi + +cleanup 22 + +# ------------------------------- Test 22 ------------------------------------ +# different base name, so it should not find the file +preptest differenttest.log 22 1 +$RLR test-config.22 --force 2>error.log + +if [ $? = 0 ]; then + echo "Logrotate exited with zero exit code, but it should not" +fi + +grep "error: stat of" error.log >/dev/null +if [ $? != 0 ]; then + echo "No error printed, but there should be one." + exit 3 +fi + +cleanup 23 + +# ------------------------------- Test 23 ------------------------------------ +# symlinks - symlinks rotation is not allowed for security reasons. +preptest test.log.original 23 1 +ln -s test.log.original test.log +$RLR test-config.23 --force 2>error.log + +checkoutput <<EOF +test.log 0 zero +test.log.original 0 zero +EOF + +rm -f test.log 2>/dev/null || true + +cleanup 24 + +# ------------------------------- Test 24 ------------------------------------ +# symlinks 2 - now copytruncate is used, but symlinks rotation is not allowed for +# security reasons. +# since logrotate-3.8.2, we don't support symlinks rotation officially. +preptest test.log.original 24 1 +ln -s test.log.original test.log +$RLR test-config.24 --force 2>error.log + +checkoutput <<EOF +test.log 0 zero +test.log.original 0 zero +EOF + +rm -f test.log 2>/dev/null || true + +cleanup 25 + +# ------------------------------- Test 25 ------------------------------------ +# If there is no '{' character after log files definition, error should be printed +# and config file should be skipped + +preptest test.log 25 1 0 +# log with 1 byte should not be rotated +$RLR test-config.25 2>error.log + +grep "missing '{' after log files definition" error.log >/dev/null +if [ $? != 0 ]; then + echo "No error printed, but there should be one." + exit 3 +fi + +rm error.log + +checkoutput <<EOF +test.log 0 zero +EOF + +cleanup 26 + +# ------------------------------- Test 26 ------------------------------------ +# If there is error in config file, log should not be rotated and original log +# should be untouched + +preptest test.log 26 1 0 +# log with 1 byte should not be rotated +$RLR test-config.26 2>error.log + +grep "unknown option" error.log >/dev/null +if [ $? != 0 ]; then + echo "No error printed, but there should be one." + exit 3 +fi + +rm error.log + +checkoutput <<EOF +test.log 0 +test.log.1 0 zero +EOF + +cleanup 27 + +# ------------------------------- Test 27 ------------------------------------ +# logrotate fails to find the correct file to mail, when using "mailfirst" in +# combination with "delaycompress" and "dateext" option. +preptest test.log 27 1 0 + +DATESTRING=$(/bin/date +%Y%m%d) + +$RLR test-config.27 --force +checkoutput <<EOF +test.log 0 +test.log-$DATESTRING 0 zero +EOF + +checkmail test.log-$DATESTRING zero + + +cleanup 28 + +# ------------------------------- Test 28 ------------------------------------ +# { on new line + +preptest test.log 28 1 0 +# log with 1 byte should not be rotated +$RLR test-config.28 + +checkoutput <<EOF +test.log 0 +test.log.1 0 zero +EOF + +cleanup 29 + +# ------------------------------- Test 29 ------------------------------------ +# { } on the same line + +preptest test.log 29 1 0 +# log with 1 byte should not be rotated +$RLR test-config.29 --force + +checkoutput <<EOF +test.log 0 +test.log.1 0 zero +EOF + +cleanup 30 + +# ------------------------------- Test 30 ------------------------------------ +# the file with the same date already exists, so it should not be overwritten +# and log should not be rotated +preptest test.log 30 1 0 + +DATESTRING=$(/bin/date +%Y%m%d) +echo "one" > test.log-$DATESTRING + +$RLR test-config.30 --force +checkoutput <<EOF +test.log 0 zero +test.log-$DATESTRING 0 one +EOF + +cleanup 31 + +# ------------------------------- Test 31 ------------------------------------ +# Test mode in create option +preptest test.log 31 1 0 + +$RLR test-config.31 --force + +stat -c %f test.log|grep 8180 >/dev/null +if [ $? != 0 ]; then + echo "Bad mode of test.log, should be 0600" + exit 3 +fi + +checkoutput <<EOF +test.log 0 +test.log.1 0 zero +EOF + +if [ $ACL_TESTS = 0 ]; then + echo "Skipping test 32: no ACL support" +else + +cleanup 32 + +# ------------------------------- Test 32 ------------------------------------ +# Without mode in 'create' directive, ACLs should be respected. +# Also check that chmod is respected when setting ACLs +preptest test.log 32 1 0 + +chmod 600 test.log +setfacl -m u:nobody:rwx test.log + +$RLR test-config.32 --force +getfacl test.log|grep "user:nobody:rwx" >/dev/null +if [ $? != 0 ]; then + echo "test.log must have user:nobody:rwx ACL" + getfacl test.log + exit 3 +fi + +getfacl test.log|grep "group::---" >/dev/null +if [ $? != 0 ]; then + echo "test.log must have group::--- ACL" + getfacl test.log + exit 3 +fi + +getfacl test.log.1|grep "user:nobody:rwx" >/dev/null +if [ $? != 0 ]; then + echo "test.log.1 must have user:nobody:rwx ACL" + getfacl test.log.1 + exit 3 +fi + +getfacl test.log.1|grep "group::---" >/dev/null +if [ $? != 0 ]; then + echo "test.log.1 must have group::--- ACL" + getfacl test.log.1 + exit 3 +fi + +checkoutput <<EOF +test.log 0 +test.log.1 0 zero +EOF + +fi + +if [ $ACL_TESTS = 0 ]; then + echo "Skipping test 33: no ACL support" +else + +cleanup 33 + +# ------------------------------- Test 33 ------------------------------------ +# With mode in 'create' directive, ACLs are overwriten by chmod +preptest test.log 33 1 0 + + +setfacl -m u:nobody:rwx test.log +$RLR test-config.33 --force + +getfacl test.log|grep "user:nobody:rwx" >/dev/null +if [ $? = 0 ]; then + echo "test.log must not contain user:nobody:rwx" + exit 3 +fi + +getfacl test.log.1|grep "user:nobody:rwx" >/dev/null +if [ $? != 0 ]; then + echo "test.log.1 must contain user:nobody:rwx" + exit 3 +fi + +checkoutput <<EOF +test.log 0 +test.log.1 0 zero +EOF + +fi + +cleanup 34 + +# ------------------------------- Test 34 ------------------------------------ +# We support changing user/mode without setting mode in create directive now +# We can't change user/group as normal user, so this test uses debug mode and +# checks the logrotate -d output. +preptest test.log 34 1 0 + +$RLR test-config.34 -d -f 2>&1|grep "uid = 0 gid = 0" > /dev/null + +if [ $? != 0 ]; then + echo "logrotate output must contain 'uid = 0 gid = 0'" + exit 3 +fi + +checkoutput <<EOF +test.log 0 zero +EOF + +if [ $ACL_TESTS = 0 ]; then + echo "Skipping test 35: no ACL support" +else + +cleanup 35 + +# ------------------------------- Test 35 ------------------------------------ +# Test 'create' directive without mode but with user/group with ACLs. ACLs should +# be respected. +preptest test.log 35 1 0 + +setfacl -m u:nobody:rwx test.log +$RLR test-config.35 --force + +getfacl test.log|grep "user:nobody:rwx" >/dev/null +if [ $? != 0 ]; then + echo "test.log must contain user:nobody:rwx" + getfacl test.log + exit 3 +fi + +getfacl test.log.1|grep "user:nobody:rwx" >/dev/null +if [ $? != 0 ]; then + echo "test.log.1 must contain user:nobody:rwx" + getfacl test.log.1 + exit 3 +fi + +checkoutput <<EOF +test.log 0 +test.log.1 0 zero +EOF + +fi + +cleanup 36 + +# ------------------------------- Test 36 ------------------------------------ +# size 1x - 'x' is unknown unit, config should be skipped +preptest test.log 36 1 0 + +$RLR test-config.36 --force 2>error.log + +grep "unknown unit" error.log >/dev/null +if [ $? != 0 ]; then + echo "No error 'unknown unit' printed, but there should be one." + exit 3 +fi + +checkoutput <<EOF +test.log 0 zero +EOF + +cleanup 37 + +# ------------------------------- Test 37 ------------------------------------ +# skip config with firstaction script +preptest test.log 37 1 0 + +$RLR test-config.37 --force 2>error.log + +grep "skipping" error.log >/dev/null +if [ $? != 0 ]; then + echo "No error 'skipping' printed, but there should be one." + exit 3 +fi + +checkoutput <<EOF +test.log 0 +test.log.1 0 zero +scriptout 0 second +EOF + +cleanup 38 + +# ------------------------------- Test 38 ------------------------------------ +# preremove script +preptest test.log 38 1 +preptest test2.log 38 1 +$RLR test-config.38 --force + +# Check both possible orders +grep "test2.log.1test.log.1" scriptout >/dev/null +if [ $? != 0 ]; then + grep "test.log.1test2.log.1" scriptout >/dev/null + if [ $? != 0 ]; then + echo "ERROR: scriptout should contain 'test2.log.1test.log.1' or 'test.log.1test2.log.1'" + exit 3 + fi +fi + +rm -f scriptout + +checkoutput <<EOF +test.log 0 +test2.log 0 +EOF + +cleanup 39 + +# ------------------------------- Test 39 ------------------------------------ +# preremove script error - do not remove log file +preptest test.log 39 1 +preptest test2.log 39 1 +$RLR test-config.39 --force 2>error.log + +grep "error running preremove script" error.log >/dev/null +if [ $? != 0 ]; then + echo "No error 'error running preremove script' printed, but there should be one." + exit 3 +fi + +# Check both possible orders +grep "test2.log.1" scriptout >/dev/null +if [ $? != 0 ]; then + grep "test.log.1" scriptout >/dev/null + if [ $? != 0 ]; then + echo "ERROR: scriptout should contain 'test2.log.1' or 'test.log.1'" + exit 3 + fi +fi + +rm -f scriptout + +checkoutput <<EOF +test.log 0 +test.log.1 0 zero +test2.log 0 +test2.log.1 0 zero +EOF + +cleanup 40 + +# ------------------------------- Test 40 ------------------------------------ +# test tabooext parsing and implementation, config.v and config.x should not be +# loaded. +preptest test.log 40 1 +mkdir -p testingdir +echo 1 > ./testingdir/config.v +echo 2 > ./testingdir/config.x + +$RLR test-config.40 --force + +rm -rf testingdir + +cleanup 41 + +# ------------------------------- Test 41 ------------------------------------ +# Test that prerotate and postrotate scripts are called only when the log files +# are actually rotated +preptest test.log 41 1 +echo x > test2.log + +$RLR test-config.41 + +checkoutput <<EOF +test.log 0 +test.log.1 0 zero +test2.log 0 x +scriptout 0 test.log;test.log; +EOF + +cleanup 42 + +# ------------------------------- Test 42 ------------------------------------ +# Test that script is called only once when sharedscripts is defined +preptest test.log 42 1 +echo number2 > test2.log + +$RLR test-config.42 + +checkoutput <<EOF +test.log 0 +test.log.1 0 zero +test2.log 0 +test2.log.1 0 number2 +scriptout 0 "test*.log ;test*.log ;" +EOF + +cleanup 43 + +# ------------------------------- Test 43 ------------------------------------ +# Test that prerotate and postrotate scripts are called twice for two files +# when sharedscripts defined +preptest test.log 43 1 +echo number2 > test2.log + +$RLR test-config.43 + +# Check both possible orders +grep "test2.log;test2.log;test.log;test.log;" scriptout >/dev/null +if [ $? != 0 ]; then + grep "test.log;test.log;test2.log;test2.log;" scriptout >/dev/null + if [ $? != 0 ]; then + echo "ERROR: scriptout should contain 'test2.log;test2.log;test.log;test.log;' or 'test.log;test.log;test2.log;test2.log;'" + exit 3 + fi +fi + +rm -f scriptout + +checkoutput <<EOF +test.log 0 +test.log.1 0 zero +test2.log 0 +test2.log.1 0 number2 +EOF + +cleanup 44 + +# ------------------------------- Test 44 ------------------------------------ +# Test that prerotate and postrotate scripts are called once when nosharedscripts +# is defined and one rotation fails +preptest test.log 44 1 + +$RLR test-config.44 2>error.log + +grep "error: stat of" error.log >/dev/null +if [ $? != 0 ]; then + echo "No error printed, but there should be one." + exit 3 +fi + +rm -f error.log + +checkoutput <<EOF +test.log 0 +test.log.1 0 zero +scriptout 0 "test.log;test.log;" +EOF + +cleanup 45 + +# ------------------------------- Test 45 ------------------------------------ +# Test that prerotate and postrotate scripts are not called when sharedscripts +# is defined and one rotation fails +preptest test.log 45 1 + +touch scriptout +$RLR test-config.45 2>error.log + +grep "error: stat of" error.log >/dev/null +if [ $? != 0 ]; then + echo "No error printed, but there should be one." + exit 3 +fi + +rm -f error.log + +checkoutput <<EOF +test.log 0 zero +scriptout 0 +EOF + +cleanup 46 + +# ------------------------------- Test 46 ------------------------------------ +# the state file is truncated and obviously corrupt +preptest test.log 46 1 + +cat > state << EOF +logrotate state -- version 1 +"$PWD/test.log" 2000-1-1 +"$PWD/test2.l +EOF + +DATESTRING=$(/bin/date +%Y%m%d) +$RLR test-config.46 2>error.log + +grep "error: bad line 3 in state file state" error.log >/dev/null +if [ $? != 0 ]; then + echo "No error printed, but there should be one." + exit 3 +fi + +rm -f error.log + +checkoutput <<EOF +test.log 0 +EOF + + +if [ $SELINUX_TESTS = 1 ]; then + chcon --type=logrotate_tmp_t test.log +else + echo "Skipping SELinux part of test 46" +fi + +cleanup 47 + +if [ $SELINUX_TESTS = 1 ]; then + +# ------------------------------- Test 47 ------------------------------------ +# test that newly created state file has the same SELinux context as the +# previous one +preptest test.log 47 1 + +cat > state << EOF +logrotate state -- version 2 +EOF + +chcon --type=logrotate_tmp_t state + +$RLR test-config.47 + +ls -Z state|grep logrotate_tmp_t >/dev/null +if [ $? != 0 ]; then + echo "state file should have selinux context logrotate_tmp_t." + exit 3 +fi + + +checkoutput <<EOF +test.log 0 zero +EOF + +else + echo "Skipping SELinux test 47" +fi + +cleanup 48 + +if [ $ACL_TESTS = 0 ]; then + echo "Skipping test 48: no ACL support" +else + +# ------------------------------- Test 48 ------------------------------------ +# Test that state file keeps the set ACLs +preptest test.log 48 1 0 + +cat > state << EOF +logrotate state -- version 2 +EOF + +setfacl -m u:nobody:rwx state + +$RLR test-config.48 + +getfacl state|grep "user:nobody:rwx" >/dev/null +if [ $? != 0 ]; then + echo "state file must have acls user:nobody:rwx" + exit 3 +fi + +checkoutput <<EOF +test.log 0 +test.log.1 0 zero +EOF + +fi + +cleanup 49 + +# ------------------------------- Test 49 ------------------------------------ +# Test that state files without hours/minutes/seconds still works properly +preptest test.log 49 1 0 + +cat > state << EOF +logrotate state -- version 2 +"test.log" 2012-8-19 +EOF + +$RLR test-config.49 + +checkoutput <<EOF +test.log 0 +test.log.1 0 zero +EOF + +cleanup 50 + +# ------------------------------- Test 50 ------------------------------------ +# test that hourly rotation works properly +preptest test.log 50 1 0 + +DATESTRING=$(/bin/date +%Y%m%d%H) +NOW=$(/bin/date "+%Y-%-m-%-d-%-H" 2>/dev/null) +HOURAGO=$(/bin/date "+%Y-%-m-%-d-%-H" --date "1 hour ago" 2>/dev/null) +GNUDATE=$? + +# --force to trigger rotation +$RLR test-config.50 --force +checkoutput <<EOF +test.log 0 +test.log-$DATESTRING 0 zero +EOF + +# It should not rotate this hour again +echo second > test.log +rm -f test.log-$DATESTRING +$RLR test-config.50 +checkoutput <<EOF +test.log 0 second +EOF + +if [ -f test.log.1 ]; then + echo "file $file does exist!" + exit 2 +fi + +if [ $GNUDATE = 0 ]; then +# Simulate previous rotation by editing state file. This should overwrite +# our previously rotated log +sed -i "s,$NOW,$HOURAGO,g" state +$RLR test-config.50 +checkoutput <<EOF +test.log 0 +test.log-$DATESTRING 0 second +EOF +else +echo "Does not have GNU Date, skipping part of this test" +fi + +cleanup 51 + +# ------------------------------- Test 51 ------------------------------------ +# regression in 3.8.4, logrotate crashes with sharedscripts when 0 logs rotated +preptest test.log 51 1 0 + +# It's memory corruption and without something in state file, it won't crash +# reliably. It would be better to run valgrind here and check the errors, but +# I don't want the test-suite to depend on valgrind... +cat > state << EOF +logrotate state -- version 2 +"/var/log/httpd/backend_error_log" 2013-6-16 +"/var/log/tokyotyrant/*.log" 2011-5-30 +"/var/log/mailman/digest" 2011-5-30 +"/var/log/piranha/piranha-gui-access" 2011-5-30 +"/var/log/boincerr.log" 2011-5-30 +"/var/log/btmp" 2013-7-9 +"/var/log/httpd/a_log" 2011-11-15 +"/var/log/cups/*_log" 2012-7-19 +"/var/log/rabbitmq/*.log" 2011-5-30 +"/var/log/func/func.log" 2011-11-17 +"/var/log/wtmp" 2013-7-9 +"/var/log/glusterfs/*glusterd.vol.log" 2011-11-17 +"/var/log/imapd.log" 2011-5-30 +"/var/log/cobbler/cobbler.log" 2011-11-6 +"/var/log/httpd/ssl_access_log" 2013-3-27 +"/var/log/mrepo.log" 2011-5-30 +EOF + +$RLR test-config.51 + +if [ $? != 0 ]; then + echo "logrotate ended with non-zero exit code (probably crashed)" + exit 3 +fi + +cleanup 52 + +# ------------------------------- Test 52 ------------------------------------ +# sharedscripts are not run if the first log file does not exist +preptest test.log 52 1 0 + +$RLR test-config.52 + +checkoutput <<EOF +test.log 0 +test.log.1 0 zero +scriptout 0 foo +EOF + +cleanup 53 + +# ------------------------------- Test 53 ------------------------------------ +# test if --force works +preptest test.log 53 1 0 + +$RLR test-config.53 --force + +checkoutput <<EOF +test.log 0 +test.log.1 0 zero +EOF + +cleanup 54 + +# ------------------------------- Test 54 ------------------------------------ +# removing last log file when using %Y-%m-%d +rm -f *test.log* +preptest test.log 54 1 0 + +DATE="" +for i in $(seq 1 60) +do + DATE=$(/bin/date "+%Y-%m-%d" --date "$i day ago" 2>/dev/null) + echo "x" > test.log-$DATE +done + +$RLR test-config.54 --force + +if [ -e test.log-$DATE ]; then + echo "File test.log-$DATE should not exist (it should be deleted)" + exit 3 +fi + +rm -f *test.log* + +cleanup 55 + +# ------------------------------- Test 55 ------------------------------------ +# removing last log file when using %s and hourly +rm -f *test.log* +preptest test.log 55 1 0 + +DATE="" +for i in $(seq 1 60) +do + DATE=$(/bin/date "+%s" --date "$i hour ago" 2>/dev/null) + echo "x" > test.log-$DATE.gz +done + +$RLR test-config.55 --force + +if [ -e test.log-$DATE.gz ]; then + echo "File test.log-$DATE.gz should not exist (it should be deleted)" + exit 3 +fi + +rm -f *test.log* + +cleanup 56 + +# ------------------------------- Test 56 ------------------------------------ +# removing last log file when using %d-%m-%Y +rm -f *test.log* +preptest test.log 56 1 0 + +DATE="" +for i in $(seq 1 60) +do + DATE=$(/bin/date "+%d-%m-%Y" --date "$i day ago" 2>/dev/null) + echo "x" > test.log-$DATE +done + +$RLR test-config.56 --force + +if [ -e test.log-$DATE ]; then + echo "File test.log-$DATE should not exist (it should be deleted)" + exit 3 +fi + +rm -f *test.log* + +cleanup 57 + +# ------------------------------- Test 57 ------------------------------------ +# When compressing program prints something to stderr, we should prepend it +# with the log name. +preptest test.log 57 1 +$RLR test-config.57 --force 2>error.log + +grep "error: Compressing" error.log >/dev/null +if [ $? != 0 ]; then + cat error.log + echo "No error printed, but there should be one." + exit 3 +fi + +grep "compression error" error.log >/dev/null +if [ $? != 0 ]; then + cat error.log + echo "No error printed, but there should be one." + exit 3 +fi + +rm -f error.log + +checkoutput <<EOF +test.log 0 +test.log.1.gz 1 zero +EOF + +cleanup 58 + +# ------------------------------- Test 58 ------------------------------------ +# Test renamecopy +preptest test.log 58 1 0 +$RLR test-config.58 --force + +checkoutput <<EOF +test.log 0 +test.log.1 0 zero +EOF + +cleanup 59 + +# ------------------------------- Test 59 ------------------------------------ +# Test renamecopy in debug mode, nothing should happen +preptest test.log 58 1 0 +touch test.log.1 +touch test.log.2 +$RLR test-config.58 --force -d 2>/dev/null + +checkoutput <<EOF +test.log 0 zero +EOF + +rm -f test.log.1 +rm -f test.log.2 + +cleanup 60 + +# ------------------------------- Test 60 ------------------------------------ +# Test we log debug output using -l option when passed. +preptest test.log 61 1 0 + +$RLR test-config.61 --force -l ./logrotate.log + +DATESTRING=$(/bin/date +%Y-%m-%d-%H) + +grep "reading config file test-config.61" logrotate.log >/dev/null +if [ $? != 0 ]; then + echo "There is no log output in logrotate.log" + exit 3 +fi + +rm -f logrotate.log + +checkoutput <<EOF +test.log 0 +test.log.$DATESTRING 0 zero +EOF + +cleanup 61 + +# ------------------------------- Test 61 ------------------------------------ +preptest test.log 61 1 0 + +$RLR test-config.61 --force + +DATESTRING=$(/bin/date +%Y-%m-%d-%H) + +checkoutput <<EOF +test.log 0 +test.log.$DATESTRING 0 zero +EOF + +cleanup 62 + +# ------------------------------- Test 62 ------------------------------------ +# Rotate sparse file +preptest test.log 24 1 0 + +printf zero > test.log +truncate -s 10M test.log +echo x >> test.log + +cp test.log test.example + +SIZE_SPARSE_OLD=$(du test.log|awk '{print $1}') +SIZE_OLD=$(du --apparent-size test.log|awk '{print $1}') +$RLR test-config.24 --force +SIZE_NEW=$(du --apparent-size test.log.1|awk '{print $1}') +SIZE_SPARSE_NEW=$(du test.log.1|awk '{print $1}') + +if [ $SIZE_OLD != $SIZE_NEW ]; then + echo "Bad apparent size of sparse logs" + echo "test.log: $SIZE_OLD" + echo "test.log.1: $SIZE_NEW" + exit 3 +fi + +if [ $SIZE_SPARSE_OLD -gt 100 ] || [ $SIZE_SPARSE_NEW -gt 100 ]; then + echo "Bad size of sparse logs" + echo "test.log: $SIZE_SPARSE_OLD" + echo "test.log.1: $SIZE_SPARSE_NEW" + exit 3 +fi + +checkoutput <<EOF +test.log 0 +test.log.1 0 zerox +EOF + +cleanup 63 + +# ------------------------------- Test 63 ------------------------------------ +# Rotate sparse file, no data should be lost when hole is in the end of file +preptest test.log 24 1 0 + +printf zero > test.log +truncate -s 10M test.log + +cp test.log test.example + +SIZE_SPARSE_OLD=$(du test.log|awk '{print $1}') +SIZE_OLD=$(du --apparent-size test.log|awk '{print $1}') +$RLR test-config.24 --force +SIZE_NEW=$(du --apparent-size test.log.1|awk '{print $1}') +SIZE_SPARSE_NEW=$(du test.log.1|awk '{print $1}') + +if [ $SIZE_OLD != $SIZE_NEW ]; then + echo "Bad apparent size of sparse logs" + echo "test.log: $SIZE_OLD" + echo "test.log.1: $SIZE_NEW" + exit 3 +fi + +if [ $SIZE_SPARSE_OLD -gt 100 ] || [ $SIZE_SPARSE_NEW -gt 100 ]; then + echo "Bad size of sparse logs" + echo "test.log: $SIZE_SPARSE_OLD" + echo "test.log.1: $SIZE_SPARSE_NEW" + exit 3 +fi + +checkoutput <<EOF +test.log 0 +test.log.1 0 zero +EOF + +cleanup 64 + +# ------------------------------- Test 64 ------------------------------------ +# filename in mail's subject with compress directive and maillast directive +# should be the name of the removed log +preptest test.log 64 1 0 + +DATESTRING=$(/bin/date +%Y%m%d) + +$RLR test-config.64 --force +checkoutput <<EOF +test.log 0 +EOF + +checkmail test.log-$DATESTRING.gz zero + +cleanup 65 + +# ------------------------------- Test 65 ------------------------------------ +# filename in mail's subject without compress directive and maillast directive +# should be the name of the removed log +preptest test.log 65 1 0 + +DATESTRING=$(/bin/date +%Y%m%d) + +$RLR test-config.65 --force +checkoutput <<EOF +test.log 0 +EOF + +checkmail test.log-$DATESTRING zero + +cleanup 66 + +# ------------------------------- Test 66 ------------------------------------ +# When using %Y in the dateformat, the old logs are not removed +preptest test.log 66 1 0 + +DATESTRING=$(/bin/date +%Y%m%d) +DAYAGO=$(/bin/date "+%Y-%m-%d" --date "1 day ago" 2>/dev/null) + +echo removed > "test.log$DAYAGO" + +$RLR test-config.66 --force +checkoutput <<EOF +test.log 0 +EOF + +if [ -f test.log$DAYAGO ]; then + echo "file $file does exist!" + exit 2 +fi + +cleanup 67 + +# ------------------------------- Test 67 ------------------------------------ +# firstaction and lastaction scripts should be called if no file is rotated +preptest test.log 67 1 0 + +DATESTRING=$(/bin/date +%Y%m%d) +TODAY=$(/bin/date "+%Y-%m-%d" 2>/dev/null) + +echo removed > "test.log$TODAY" + +$RLR test-config.67 --force + +cat scriptout|grep firstaction >/dev/null +if [ $? != 0 ]; then + echo "scriptout should contain 'firstaction'" + exit 3 +fi + +cat scriptout|grep lastaction >/dev/null +if [ $? != 0 ]; then + echo "scriptout should contain 'lastaction'" + exit 3 +fi + +cleanup 68 + +# ------------------------------- Test 68 ------------------------------------ +# Old state file entries should be removed when not used. Logrotate should +# not freeze on big state file. +preptest test.log 68 1 0 + +cat > state << EOF +logrotate state -- version 1 +"$PWD/test.log" 2000-1-1 +EOF + +for i in $(seq 1 200000) +do + echo "\"$PWD/removed.log$i\" 2000-1-1" >> state +done + +$RLR test-config.68 --force + +cat state|grep test.log >/dev/null +if [ $? != 0 ]; then + echo "state file should contain 'test.log'" + exit 3 +fi + +cat state|grep removed.log >/dev/null +if [ $? = 0 ]; then + echo "state file should not contain 'removed.log'" + exit 3 +fi + +cleanup 69 + +# ------------------------------- Test 69 ------------------------------------ +# Test olddir with wildcard in the pattern +preptest test.log 69 1 0 +rm -rf testdir adir bdir +mkdir adir +mkdir bdir +cp test.log adir +cp test.log bdir +$RLR test-config.69 --force -v + +checkoutput <<EOF +adir/test.log 0 +testdir/test.log.1 0 zero +EOF + +rm -rf testdir adir +rm -rf testdir bdir + +cleanup 70 + +# ------------------------------- Test 70 ------------------------------------ +# No rotation should occur because file is too young +preptest test.log 70 2 + +# Put in place a state file that will force a rotation +cat > state <<EOF +logrotate state -- version 2 +"$PWD/test.log" 2000-1-1 +EOF + +$RLR test-config.70 + +checkoutput <<EOF +test.log 0 zero +test.log.1 0 first +EOF + +cleanup 71 + +# ------------------------------- Test 71 ------------------------------------ +# Rotation should occur because file is old +preptest test.log 71 2 + +# Set log modification time to some date in the past +touch -t 200001010000 test.log + +# Put in place a state file that will force a rotation +cat > state <<EOF +logrotate state -- version 2 +"$PWD/test.log" 2000-1-1 +EOF + +$RLR test-config.71 + +checkoutput <<EOF +test.log 0 +test.log.1 0 zero +test.log.2 0 first +EOF + +cleanup 72 + +# ------------------------------- Test 72 ------------------------------------ +preptest test.log 72 2 + +$RLR test-config.72 --force + +checkoutput <<EOF +test.log 0 +test.log.1 0 zero +test.log.2.gz 1 first +EOF + +echo 'unexpected' > test.log.1.gz + +$RLR test-config.72 --force +dt="$(date +%Y%m%d%H)" + +checkoutput <<EOF +test.log 0 +test.log.1 0 +test.log.1.gz-$dt.backup 0 unexpected +test.log.2.gz 1 zero +test.log.3.gz 1 first +EOF + +cleanup 100 + +# ------------------------------- Test 100 ------------------------------------ +# check rotation with extension appended to the filename +preptest test.log 100 1 0 +$RLR test-config.100 --force + +checkoutput <<EOF +test.log 0 +test.log.1.newext 0 zero +EOF + +# check rotation with extension moved after the number +cleanup 101 + +preptest test.log 101 1 0 +$RLR test-config.101 --force + +checkoutput <<EOF +test.log 0 +test.1.log 0 zero +EOF + + +cleanup +
diff --git a/logrotate/test/test-config.1.in b/logrotate/test/test-config.1.in new file mode 100644 index 0000000..46651db --- /dev/null +++ b/logrotate/test/test-config.1.in
@@ -0,0 +1,9 @@ +create + +&DIR&/test.log { + daily + # note the white space after this line + rotate 2 + mail user@myhost.org + maillast +}
diff --git a/logrotate/test/test-config.10.in b/logrotate/test/test-config.10.in new file mode 100644 index 0000000..2bbdf70 --- /dev/null +++ b/logrotate/test/test-config.10.in
@@ -0,0 +1,11 @@ +create + +&DIR&/test.log { + daily + rotate 3 + compress + delaycompress + create + mailfirst + mail user@myhost.org +}
diff --git a/logrotate/test/test-config.100.in b/logrotate/test/test-config.100.in new file mode 100644 index 0000000..da99f89 --- /dev/null +++ b/logrotate/test/test-config.100.in
@@ -0,0 +1,7 @@ +create + +&DIR&/test.log { + monthly + rotate 1 + addextension .newext +}
diff --git a/logrotate/test/test-config.101.in b/logrotate/test/test-config.101.in new file mode 100644 index 0000000..c5f8951 --- /dev/null +++ b/logrotate/test/test-config.101.in
@@ -0,0 +1,7 @@ +create + +&DIR&/test.log { + monthly + rotate 1 + addextension .log +}
diff --git a/logrotate/test/test-config.11.in b/logrotate/test/test-config.11.in new file mode 100644 index 0000000..1c235ed --- /dev/null +++ b/logrotate/test/test-config.11.in
@@ -0,0 +1,17 @@ +create + +compress + +&DIR&/test.log { + monthly + rotate 1 + mail user@myhost.org + maillast + sharedscripts + + postrotate + touch scriptout + echo $(cat scriptout) foo > foo + mv foo scriptout + endscript +}
diff --git a/logrotate/test/test-config.12.in b/logrotate/test/test-config.12.in new file mode 100644 index 0000000..b35e139 --- /dev/null +++ b/logrotate/test/test-config.12.in
@@ -0,0 +1,7 @@ +create + +&DIR&/test.log { + monthly + rotate 1 + olddir testdir +}
diff --git a/logrotate/test/test-config.13.in b/logrotate/test/test-config.13.in new file mode 100644 index 0000000..dc2efd5 --- /dev/null +++ b/logrotate/test/test-config.13.in
@@ -0,0 +1,8 @@ +create + +&DIR&/test.log { + monthly + rotate 1 + olddir &DIR&/testdir + createolddir 700 &USER& &GROUP& +}
diff --git a/logrotate/test/test-config.14.in b/logrotate/test/test-config.14.in new file mode 100644 index 0000000..d6b8722 --- /dev/null +++ b/logrotate/test/test-config.14.in
@@ -0,0 +1,8 @@ +create + +&DIR&/test.log { + daily + dateext + dateformat .%Y-%m-%d + rotate 1 +}
diff --git a/logrotate/test/test-config.15.in b/logrotate/test/test-config.15.in new file mode 100644 index 0000000..13b43b0 --- /dev/null +++ b/logrotate/test/test-config.15.in
@@ -0,0 +1,8 @@ +create + +&DIR&/test.log { + daily + shred + shredcycles 20 + rotate 1 +}
diff --git a/logrotate/test/test-config.16.in b/logrotate/test/test-config.16.in new file mode 100644 index 0000000..cc42fa8 --- /dev/null +++ b/logrotate/test/test-config.16.in
@@ -0,0 +1,7 @@ +create + +&DIR&/test.log { + weekly + maxsize 4 + rotate 1 +}
diff --git a/logrotate/test/test-config.17.in b/logrotate/test/test-config.17.in new file mode 100644 index 0000000..a33eaa0 --- /dev/null +++ b/logrotate/test/test-config.17.in
@@ -0,0 +1,8 @@ +create + +# missing { is OK, we're testing parsing here +&DIR&/test.log + weekly + maxsize 4 + rotate 1 +}
diff --git a/logrotate/test/test-config.18.in b/logrotate/test/test-config.18.in new file mode 100644 index 0000000..ee2fc3f --- /dev/null +++ b/logrotate/test/test-config.18.in
@@ -0,0 +1,10 @@ +create + +# tests more compressoptions +&DIR&/test.log { + compress + compresscmd ./compress + compressoptions -f -9 + weekly + rotate 1 +}
diff --git a/logrotate/test/test-config.19.in b/logrotate/test/test-config.19.in new file mode 100644 index 0000000..5f5fe03 --- /dev/null +++ b/logrotate/test/test-config.19.in
@@ -0,0 +1,9 @@ +create + +&DIR&/test*.log { + daily + rotate 1 + postrotate + exit 1 + endscript +}
diff --git a/logrotate/test/test-config.2.in b/logrotate/test/test-config.2.in new file mode 100644 index 0000000..fb5500f --- /dev/null +++ b/logrotate/test/test-config.2.in
@@ -0,0 +1,6 @@ +"&DIR&/test.log" { + monthly + rotate 2 + mail user@myhost.org + maillast +}
diff --git a/logrotate/test/test-config.20.in b/logrotate/test/test-config.20.in new file mode 100644 index 0000000..d2a17b4 --- /dev/null +++ b/logrotate/test/test-config.20.in
@@ -0,0 +1,10 @@ +create + +&DIR&/test*.log { + daily + rotate 1 + sharedscripts + postrotate + exit 1 + endscript +}
diff --git a/logrotate/test/test-config.21.in b/logrotate/test/test-config.21.in new file mode 100644 index 0000000..7bca5c2 --- /dev/null +++ b/logrotate/test/test-config.21.in
@@ -0,0 +1,7 @@ +create + +&DIR&/test*.log { + daily + rotate 1 + missingok +}
diff --git a/logrotate/test/test-config.22.in b/logrotate/test/test-config.22.in new file mode 100644 index 0000000..d98ac86 --- /dev/null +++ b/logrotate/test/test-config.22.in
@@ -0,0 +1,6 @@ +create + +&DIR&/test*.log { + daily + rotate 1 +}
diff --git a/logrotate/test/test-config.23.in b/logrotate/test/test-config.23.in new file mode 100644 index 0000000..d98ac86 --- /dev/null +++ b/logrotate/test/test-config.23.in
@@ -0,0 +1,6 @@ +create + +&DIR&/test*.log { + daily + rotate 1 +}
diff --git a/logrotate/test/test-config.24.in b/logrotate/test/test-config.24.in new file mode 100644 index 0000000..35cfcd3 --- /dev/null +++ b/logrotate/test/test-config.24.in
@@ -0,0 +1,7 @@ +create + +&DIR&/test*.log { + daily + copytruncate + rotate 1 +}
diff --git a/logrotate/test/test-config.25.in b/logrotate/test/test-config.25.in new file mode 100644 index 0000000..1e5b077 --- /dev/null +++ b/logrotate/test/test-config.25.in
@@ -0,0 +1,7 @@ +create + +# missing { is OK, we're testing parsing here +&DIR&/test.log + weekly + maxsize 4 + rotate 1
diff --git a/logrotate/test/test-config.26.in b/logrotate/test/test-config.26.in new file mode 100644 index 0000000..3c820fb --- /dev/null +++ b/logrotate/test/test-config.26.in
@@ -0,0 +1,7 @@ +create + +&DIR&/test.log { + waeekly + maxsize 4 + rotate 1 +} \ No newline at end of file
diff --git a/logrotate/test/test-config.27.in b/logrotate/test/test-config.27.in new file mode 100644 index 0000000..eac0232 --- /dev/null +++ b/logrotate/test/test-config.27.in
@@ -0,0 +1,13 @@ +create + +&DIR&/test.log { + daily + rotate 999 + compress + dateext + ifempty + delaycompress + + mailfirst + mail user@myhost.org +}
diff --git a/logrotate/test/test-config.28.in b/logrotate/test/test-config.28.in new file mode 100644 index 0000000..96479d6 --- /dev/null +++ b/logrotate/test/test-config.28.in
@@ -0,0 +1,9 @@ +create + +#testing { on new line +&DIR&/test.log +{ + weekly + maxsize 4 + rotate 1 +}
diff --git a/logrotate/test/test-config.29.in b/logrotate/test/test-config.29.in new file mode 100644 index 0000000..b3123d6 --- /dev/null +++ b/logrotate/test/test-config.29.in
@@ -0,0 +1,6 @@ +create +rotate 1 +daily + +#testing { } on the same line +&DIR&/test.log { }
diff --git a/logrotate/test/test-config.3.in b/logrotate/test/test-config.3.in new file mode 100644 index 0000000..eb081f9 --- /dev/null +++ b/logrotate/test/test-config.3.in
@@ -0,0 +1,14 @@ +create + +&DIR&/test*.log { + monthly + rotate 1 + mail user@myhost.org + maillast + + postrotate + touch scriptout + echo $(cat scriptout) foo > foo + mv foo scriptout + endscript +}
diff --git a/logrotate/test/test-config.30.in b/logrotate/test/test-config.30.in new file mode 100644 index 0000000..154fb56 --- /dev/null +++ b/logrotate/test/test-config.30.in
@@ -0,0 +1,7 @@ +create + +&DIR&/test.log { + daily + rotate 999 + dateext +}
diff --git a/logrotate/test/test-config.31.in b/logrotate/test/test-config.31.in new file mode 100644 index 0000000..c1f0e22 --- /dev/null +++ b/logrotate/test/test-config.31.in
@@ -0,0 +1,6 @@ +create 0600 &USER& &GROUP& + +&DIR&/test.log { + daily + rotate 999 +}
diff --git a/logrotate/test/test-config.32.in b/logrotate/test/test-config.32.in new file mode 100644 index 0000000..1b8d7be --- /dev/null +++ b/logrotate/test/test-config.32.in
@@ -0,0 +1,6 @@ +create + +&DIR&/test.log { + daily + rotate 999 +}
diff --git a/logrotate/test/test-config.33.in b/logrotate/test/test-config.33.in new file mode 100644 index 0000000..dba4234 --- /dev/null +++ b/logrotate/test/test-config.33.in
@@ -0,0 +1,6 @@ +create 0600 + +&DIR&/test.log { + daily + rotate 999 +}
diff --git a/logrotate/test/test-config.34.in b/logrotate/test/test-config.34.in new file mode 100644 index 0000000..cae6959 --- /dev/null +++ b/logrotate/test/test-config.34.in
@@ -0,0 +1,6 @@ +create root root + +&DIR&/test.log { + daily + rotate 1 +}
diff --git a/logrotate/test/test-config.35.in b/logrotate/test/test-config.35.in new file mode 100644 index 0000000..8f34312 --- /dev/null +++ b/logrotate/test/test-config.35.in
@@ -0,0 +1,6 @@ +create &USER& &GROUP& + +&DIR&/test.log { + daily + rotate 1 +}
diff --git a/logrotate/test/test-config.36.in b/logrotate/test/test-config.36.in new file mode 100644 index 0000000..a53271f --- /dev/null +++ b/logrotate/test/test-config.36.in
@@ -0,0 +1,7 @@ +# bad unit 'x' + +&DIR&/test.log { + daily + size 300x + rotate 1 +}
diff --git a/logrotate/test/test-config.37.in b/logrotate/test/test-config.37.in new file mode 100644 index 0000000..99befbf --- /dev/null +++ b/logrotate/test/test-config.37.in
@@ -0,0 +1,23 @@ +&DIR&/test.log { + size 300x + daily + firstaction + touch scriptout + # put some } here, so we check if parser won't break on it. +} + echo $(cat scriptout) foo > foo } + mv foo scriptout + endscript + rotate 1 +} + +&DIR&/test.log { + create + daily + firstaction + touch scriptout + echo $(cat scriptout) second > foo + mv foo scriptout + endscript + rotate 1 +}
diff --git a/logrotate/test/test-config.38.in b/logrotate/test/test-config.38.in new file mode 100644 index 0000000..806799f --- /dev/null +++ b/logrotate/test/test-config.38.in
@@ -0,0 +1,14 @@ +create + +&DIR&/test*.log { + monthly + rotate 0 + mail user@myhost.org + maillast + sharedscripts + + preremove + touch scriptout + printf '%s' "$(basename $*)" >> scriptout + endscript +}
diff --git a/logrotate/test/test-config.39.in b/logrotate/test/test-config.39.in new file mode 100644 index 0000000..8e84927 --- /dev/null +++ b/logrotate/test/test-config.39.in
@@ -0,0 +1,15 @@ +create + +&DIR&/test*.log { + monthly + rotate 0 + mail user@myhost.org + maillast + sharedscripts + + preremove + touch scriptout + printf '%s' "$(basename $*)" >> scriptout + this should trigger an error + endscript +}
diff --git a/logrotate/test/test-config.4.in b/logrotate/test/test-config.4.in new file mode 100644 index 0000000..a06fa83 --- /dev/null +++ b/logrotate/test/test-config.4.in
@@ -0,0 +1,15 @@ +create + +&DIR&/test*.log { + monthly + rotate 1 + mail user@myhost.org + maillast + sharedscripts + + postrotate + touch scriptout + echo $(cat scriptout) foo > foo + mv foo scriptout + endscript +}
diff --git a/logrotate/test/test-config.40.in b/logrotate/test/test-config.40.in new file mode 100644 index 0000000..29779c0 --- /dev/null +++ b/logrotate/test/test-config.40.in
@@ -0,0 +1,5 @@ +create +tabooext .v,.sample,.unused,~ +tabooext + .x, .y, .z + +include &DIR&/testingdir
diff --git a/logrotate/test/test-config.41.in b/logrotate/test/test-config.41.in new file mode 100644 index 0000000..59dd395 --- /dev/null +++ b/logrotate/test/test-config.41.in
@@ -0,0 +1,17 @@ +create + +&DIR&/test*.log { + size 5 + rotate 1 + nosharedscripts + prerotate + touch scriptout + printf '%s' "$(basename $1)" >> scriptout + printf ";" >> scriptout + endscript + postrotate + touch scriptout + printf '%s' "$(basename $1)" >> scriptout + printf ";" >> scriptout + endscript +}
diff --git a/logrotate/test/test-config.42.in b/logrotate/test/test-config.42.in new file mode 100644 index 0000000..a28ee08 --- /dev/null +++ b/logrotate/test/test-config.42.in
@@ -0,0 +1,27 @@ +create + +&DIR&/test*.log { + size 5 + rotate 1 + sharedscripts + prerotate + touch scriptout + IFS=$(printf "\n\b") + printf "\"" >> scriptout + for f in "$1" + do + printf '%s' "$(basename $f)" >> scriptout + printf ";" >> scriptout + done + endscript + postrotate + touch scriptout + IFS=$(printf "\n\b") + for f in "$1" + do + printf '%s' "$(basename $f)" >> scriptout + printf ";" >> scriptout + done + printf "\"" >> scriptout + endscript +}
diff --git a/logrotate/test/test-config.43.in b/logrotate/test/test-config.43.in new file mode 100644 index 0000000..d8eca2f --- /dev/null +++ b/logrotate/test/test-config.43.in
@@ -0,0 +1,17 @@ +create + +&DIR&/test*.log { + size 5 + rotate 1 + nosharedscripts + prerotate + touch scriptout + printf '%s' "$(basename $1)" >> scriptout + printf ";" >> scriptout + endscript + postrotate + touch scriptout + printf '%s' "$(basename $1)" >> scriptout + printf ";" >> scriptout + endscript +}
diff --git a/logrotate/test/test-config.44.in b/logrotate/test/test-config.44.in new file mode 100644 index 0000000..6a6cdf0 --- /dev/null +++ b/logrotate/test/test-config.44.in
@@ -0,0 +1,27 @@ +create + +&DIR&/test.log &DIR&/test2.log { + size 5 + rotate 1 + nosharedscripts + prerotate + touch scriptout + IFS=$(printf "\n\b") + printf "\"" >> scriptout + for f in "$1" + do + printf '%s' "$(basename $f)" >> scriptout + printf ";" >> scriptout + done + endscript + postrotate + touch scriptout + IFS=$(printf "\n\b") + for f in "$1" + do + printf '%s' "$(basename $f)" >> scriptout + printf ";" >> scriptout + done + printf "\"" >> scriptout + endscript +}
diff --git a/logrotate/test/test-config.45.in b/logrotate/test/test-config.45.in new file mode 100644 index 0000000..284f1bf --- /dev/null +++ b/logrotate/test/test-config.45.in
@@ -0,0 +1,27 @@ +create + +&DIR&/test.log &DIR&/test2.log { + size 5 + rotate 1 + sharedscripts + prerotate + touch scriptout + IFS=$(printf "\n\b") + printf "\"" >> scriptout + for f in "$1" + do + printf '%s' "$(basename $f)" >> scriptout + printf ";" >> scriptout + done + endscript + postrotate + touch scriptout + IFS=$(printf "\n\b") + for f in "$1" + do + printf '%s' "$(basename $f)" >> scriptout + printf ";" >> scriptout + done + printf "\"" >> scriptout + endscript +}
diff --git a/logrotate/test/test-config.46.in b/logrotate/test/test-config.46.in new file mode 100644 index 0000000..154fb56 --- /dev/null +++ b/logrotate/test/test-config.46.in
@@ -0,0 +1,7 @@ +create + +&DIR&/test.log { + daily + rotate 999 + dateext +}
diff --git a/logrotate/test/test-config.47.in b/logrotate/test/test-config.47.in new file mode 100644 index 0000000..154fb56 --- /dev/null +++ b/logrotate/test/test-config.47.in
@@ -0,0 +1,7 @@ +create + +&DIR&/test.log { + daily + rotate 999 + dateext +}
diff --git a/logrotate/test/test-config.48.in b/logrotate/test/test-config.48.in new file mode 100644 index 0000000..b7d3f00 --- /dev/null +++ b/logrotate/test/test-config.48.in
@@ -0,0 +1,7 @@ +create + +&DIR&/test.log { + daily + rotate 999 + size 2 +}
diff --git a/logrotate/test/test-config.49.in b/logrotate/test/test-config.49.in new file mode 100644 index 0000000..dca1b63 --- /dev/null +++ b/logrotate/test/test-config.49.in
@@ -0,0 +1,7 @@ +create + +&DIR&/test.log { + daily + rotate 4 + size 2 +}
diff --git a/logrotate/test/test-config.5.in b/logrotate/test/test-config.5.in new file mode 100644 index 0000000..dfc7890 --- /dev/null +++ b/logrotate/test/test-config.5.in
@@ -0,0 +1,15 @@ +create + +&DIR&/test.log &DIR&/anothertest.log { + monthly + rotate 1 + mail user@myhost.org + maillast + sharedscripts + + postrotate + touch scriptout + echo $(cat scriptout) foo > foo + mv foo scriptout + endscript +}
diff --git a/logrotate/test/test-config.50.in b/logrotate/test/test-config.50.in new file mode 100644 index 0000000..38bdd48 --- /dev/null +++ b/logrotate/test/test-config.50.in
@@ -0,0 +1,7 @@ +create + +&DIR&/test.log { + hourly + dateext + rotate 4 +}
diff --git a/logrotate/test/test-config.51.in b/logrotate/test/test-config.51.in new file mode 100644 index 0000000..3182525 --- /dev/null +++ b/logrotate/test/test-config.51.in
@@ -0,0 +1,8 @@ +/var/log/this_dir_does_not_exist/*log { + size 1 + missingok + sharedscripts + prerotate + /usr/bin/some-program + endscript +}
diff --git a/logrotate/test/test-config.52.in b/logrotate/test/test-config.52.in new file mode 100644 index 0000000..56beced --- /dev/null +++ b/logrotate/test/test-config.52.in
@@ -0,0 +1,13 @@ +create + +/var/log/does_not_exist.log &DIR&/test.log { + rotate 14 + size 2 + missingok + sharedscripts + postrotate + touch scriptout + echo $(cat scriptout) foo > foo + mv foo scriptout + endscript +} \ No newline at end of file
diff --git a/logrotate/test/test-config.53.in b/logrotate/test/test-config.53.in new file mode 100644 index 0000000..871fcff --- /dev/null +++ b/logrotate/test/test-config.53.in
@@ -0,0 +1,7 @@ +create + +&DIR&/test.log { + rotate 14 + size 4096 + missingok +} \ No newline at end of file
diff --git a/logrotate/test/test-config.54.in b/logrotate/test/test-config.54.in new file mode 100644 index 0000000..c946af1 --- /dev/null +++ b/logrotate/test/test-config.54.in
@@ -0,0 +1,8 @@ +create + +&DIR&/test.log { + daily + dateext + dateformat -%Y-%m-%d + rotate 60 +}
diff --git a/logrotate/test/test-config.55.in b/logrotate/test/test-config.55.in new file mode 100644 index 0000000..8b10ad1 --- /dev/null +++ b/logrotate/test/test-config.55.in
@@ -0,0 +1,21 @@ +create + +# continue and throw no error message when log file is not present +missingok + +# truncate the original log file in place after creating a copy +copytruncate + +# compress the file +compress + +# do only rotate when not empty +notifempty + +&DIR&/test.log { + hourly + dateext + dateformat -%s + rotate 60 + nosharedscripts +}
diff --git a/logrotate/test/test-config.56.in b/logrotate/test/test-config.56.in new file mode 100644 index 0000000..adaf2a5 --- /dev/null +++ b/logrotate/test/test-config.56.in
@@ -0,0 +1,8 @@ +create + +&DIR&/test.log { + daily + dateext + dateformat -%d-%m-%Y + rotate 60 +}
diff --git a/logrotate/test/test-config.57.in b/logrotate/test/test-config.57.in new file mode 100644 index 0000000..e9018b3 --- /dev/null +++ b/logrotate/test/test-config.57.in
@@ -0,0 +1,10 @@ +create + +# tests more compressoptions +&DIR&/test.log { + compress + compresscmd ./compress-error + compressoptions -f -9 + weekly + rotate 1 +}
diff --git a/logrotate/test/test-config.58.in b/logrotate/test/test-config.58.in new file mode 100644 index 0000000..34906da --- /dev/null +++ b/logrotate/test/test-config.58.in
@@ -0,0 +1,7 @@ +create + +&DIR&/test.log { + renamecopy + weekly + rotate 1 +}
diff --git a/logrotate/test/test-config.6.in b/logrotate/test/test-config.6.in new file mode 100644 index 0000000..0d3e956 --- /dev/null +++ b/logrotate/test/test-config.6.in
@@ -0,0 +1,16 @@ +create + +&DIR&/test.log &DIR&/anothertest.log { + monthly + rotate 1 + start 0 + mail user@myhost.org + maillast + sharedscripts + + postrotate + touch scriptout + echo $(cat scriptout) foo > foo + mv foo scriptout + endscript +}
diff --git a/logrotate/test/test-config.61.in b/logrotate/test/test-config.61.in new file mode 100644 index 0000000..423241f --- /dev/null +++ b/logrotate/test/test-config.61.in
@@ -0,0 +1,8 @@ +create + +&DIR&/test.log { + daily + dateext + dateformat .%Y-%m-%d-%H + rotate 1 +}
diff --git a/logrotate/test/test-config.64.in b/logrotate/test/test-config.64.in new file mode 100644 index 0000000..c817734 --- /dev/null +++ b/logrotate/test/test-config.64.in
@@ -0,0 +1,13 @@ +create + +&DIR&/test.log { + daily + dateext + dateformat -%Y%m%d + rotate 0 + compress + nosharedscripts + dateext + mail user@myhost.org + maillast +}
diff --git a/logrotate/test/test-config.65.in b/logrotate/test/test-config.65.in new file mode 100644 index 0000000..19007f5 --- /dev/null +++ b/logrotate/test/test-config.65.in
@@ -0,0 +1,12 @@ +create + +&DIR&/test.log { + daily + dateext + dateformat -%Y%m%d + rotate 0 + nosharedscripts + dateext + mail user@myhost.org + maillast +}
diff --git a/logrotate/test/test-config.66.in b/logrotate/test/test-config.66.in new file mode 100644 index 0000000..8c32687 --- /dev/null +++ b/logrotate/test/test-config.66.in
@@ -0,0 +1,10 @@ +create + +&DIR&/test.log { + daily + dateext + dateformat %Y-%m-%d + rotate 1 + nosharedscripts + dateext +}
diff --git a/logrotate/test/test-config.67.in b/logrotate/test/test-config.67.in new file mode 100644 index 0000000..69b9fff --- /dev/null +++ b/logrotate/test/test-config.67.in
@@ -0,0 +1,16 @@ +create + +&DIR&/test.log { + daily + dateext + dateformat %Y-%m-%d + rotate 1 + + firstaction + echo "firstaction" > scriptout + endscript + + lastaction + echo "lastaction" >> scriptout + endscript +}
diff --git a/logrotate/test/test-config.68.in b/logrotate/test/test-config.68.in new file mode 100644 index 0000000..e8e1c79 --- /dev/null +++ b/logrotate/test/test-config.68.in
@@ -0,0 +1,6 @@ +create + +&DIR&/test.log { + daily + rotate 1 +}
diff --git a/logrotate/test/test-config.69.in b/logrotate/test/test-config.69.in new file mode 100644 index 0000000..9752e0a --- /dev/null +++ b/logrotate/test/test-config.69.in
@@ -0,0 +1,10 @@ +create + +&DIR&/*/test.log +&DIR&/*/test.lo3 { + monthly + rotate 1 + olddir &DIR&/testdir + createolddir 700 &USER& &GROUP& + missingok +}
diff --git a/logrotate/test/test-config.7.in b/logrotate/test/test-config.7.in new file mode 100644 index 0000000..d565da2 --- /dev/null +++ b/logrotate/test/test-config.7.in
@@ -0,0 +1,16 @@ +create + +&DIR&/test.log &DIR&/anothertest.log { + monthly + rotate 3 + start 6 + mail user@myhost.org + maillast + sharedscripts + + postrotate + touch scriptout + echo $(cat scriptout) foo > foo + mv foo scriptout + endscript +}
diff --git a/logrotate/test/test-config.70.in b/logrotate/test/test-config.70.in new file mode 100644 index 0000000..0047d73 --- /dev/null +++ b/logrotate/test/test-config.70.in
@@ -0,0 +1,8 @@ +create + +&DIR&/test.log { + daily + rotate 3 + missingok + minage 5 +}
diff --git a/logrotate/test/test-config.71.in b/logrotate/test/test-config.71.in new file mode 100644 index 0000000..0047d73 --- /dev/null +++ b/logrotate/test/test-config.71.in
@@ -0,0 +1,8 @@ +create + +&DIR&/test.log { + daily + rotate 3 + missingok + minage 5 +}
diff --git a/logrotate/test/test-config.72.in b/logrotate/test/test-config.72.in new file mode 100644 index 0000000..9fe50a2 --- /dev/null +++ b/logrotate/test/test-config.72.in
@@ -0,0 +1,7 @@ +&DIR&/test.log { + daily + rotate 3 + compress + delaycompress + create +}
diff --git a/logrotate/test/test-config.8.in b/logrotate/test/test-config.8.in new file mode 100644 index 0000000..994be5f --- /dev/null +++ b/logrotate/test/test-config.8.in
@@ -0,0 +1,17 @@ +create + +compress + +&DIR&/test.log { + monthly + rotate 3 + mail user@myhost.org + mailfirst + sharedscripts + + postrotate + touch scriptout + echo $(cat scriptout) foo > foo + mv foo scriptout + endscript +}
diff --git a/logrotate/test/test-config.9.in b/logrotate/test/test-config.9.in new file mode 100644 index 0000000..0d26bd9 --- /dev/null +++ b/logrotate/test/test-config.9.in
@@ -0,0 +1,17 @@ +create + +compress + +&DIR&/test.log { + monthly + rotate 0 + mail user@myhost.org + mailfirst + sharedscripts + + postrotate + touch scriptout + echo $(cat scriptout) foo > foo + mv foo scriptout + endscript +}
diff --git a/logrotate/upload-release.sh b/logrotate/upload-release.sh new file mode 100755 index 0000000..e227bcc --- /dev/null +++ b/logrotate/upload-release.sh
@@ -0,0 +1,59 @@ +#!/bin/sh +set -x +SELF="$0" +TAG="$1" +TOKEN="$2" + +NAME="logrotate" +NV="${NAME}-${TAG}" + +usage() { + printf "Usage: %s TAG TOKEN\n" "$SELF" >&2 + exit 1 +} + +die() { + printf "%s: error: %s\n" "$SELF" >&2 + exit 1 +} + +# check arguments +test "$TAG" = "$(git describe --tags "$TAG")" || usage +test -n "$TOKEN" || usage + +# check that .tar.gz is prepared +TAR_GZ="${NV}.tar.gz" +test -e "$TAR_GZ" || die "$TAR_GZ does not exist" + +# create .tar.xz from .tar.gz +TAR_XZ="${NV}.tar.xz" +gzip -cd "$TAR_GZ" | xz -c > "$TAR_XZ" || die "failed to write $TAR_XZ" + +# file to store response from GitHub API +JSON="./${NV}-github-relase.js" + +# create a new release on GitHub +curl "https://api.github.com/repos/${NAME}/${NAME}/releases" \ + -o "$JSON" --fail --verbose \ + --header "Authorization: token $TOKEN" \ + --data '{ + "tag_name": "'"$TAG"'", + "target_commitish": "master", + "name": "'"$NV"'", + "draft": false, + "prerelease": false +}' || exit $? + +# parse upload URL from the response +UPLOAD_URL="$(grep '^ *"upload_url": "' "$JSON" \ + | sed -e 's/^ *"upload_url": "//' -e 's/{.*}.*$//')" +grep '^https://uploads.github.com/.*/assets$' <<< "$UPLOAD_URL" || exit $? + +# upload both .tar.gz and .tar.xz +for comp in gzip xz; do + file="${NV}.tar.${comp:0:2}" + curl "${UPLOAD_URL}?name=${file}" \ + -T "$file" --fail --verbose \ + --header "Authorization: token $TOKEN" \ + --header "Content-Type: application/x-${comp}" +done