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