diff --git a/data/hex_syslogd/hex_trim_syslog.sh b/data/hex_syslogd/hex_trim_syslog.sh index fa98611..fd569c8 100644 --- a/data/hex_syslogd/hex_trim_syslog.sh +++ b/data/hex_syslogd/hex_trim_syslog.sh @@ -3,22 +3,41 @@ # Drop /var/log/messages.N retentions, oldest first, until the whole messages set # fits the byte budget config_syslogd computes (SYSLOG_DISK_PERC of the partition). # -# Stops at .2 and never removes messages.1. This runs from logrotate's postrotate, -# which fires *before* compression, so messages.1 is the file logrotate is about to -# compress in this same cycle -- removing it under logrotate's feet makes the cycle -# log "unable to open /var/log/messages.1 for compression: No such file or -# directory", the same class of error installing this script was meant to stop. +# Generations are discovered from what is on disk rather than counted down from a +# literal, because both halves of the old loop had drifted out of step with the +# files it was supposed to match: +# +# - it tested `-e /var/log/messages.$I` only, and every rotated generation is +# compressed. WriteLogRotateConf always emits `compress` and the global conf sets +# no `dateext`, so at postrotate time the directory holds `messages`, a still +# uncompressed `messages.1`, and `messages.2.gz` onward. The one unsuffixed +# generation that ever exists is `.1` -- which this must not touch, see below -- +# so the loop could never match anything at all. +# - it started at I=14, mirroring the old global `rotate 14`. That is now +# `retention * 24` (336 at the default), so even with .gz matching it would have +# scanned 2..14 of up to 336 -- and a log that reaches 336 generations is exactly +# the busy log the budget exists for. +# +# Taking the list from the filesystem removes the coupling that broke twice. +# +# messages.1 is deliberately skipped. This runs from logrotate's postrotate, which +# fires *before* compression, so .1 is the file logrotate is about to compress in +# this same cycle -- removing it under logrotate's feet makes the cycle log +# "unable to open /var/log/messages.1 for compression: No such file or directory". # Nothing is lost from the budget by waiting: the next rotation renames .1 to .2, -# and this becomes eligible to remove it one cycle later. +# and it becomes eligible one cycle later. LIMIT=$1 -I=14 total=`du -c /var/log/messages* |grep total |awk '{print $1}'` -while [ $total -gt $LIMIT -a $I -gt 1 ]; do - NAME=/var/log/messages.$I - if [ -e $NAME ]; then - logger "syslog message total size exceeded the limit. Removing retentions." - rm -f $NAME - total=`du -c /var/log/messages* |grep total |awk '{print $1}'` - fi - I=$((I-1)) + +# oldest first: sort by generation number, highest first, .gz or not +for NAME in `ls /var/log/messages.* 2>/dev/null | + sed -n 's|^/var/log/messages\.\([0-9][0-9]*\)\(\.gz\)\{0,1\}$|\1 &|p' | + sort -rn | awk '{print $2}'`; do + [ $total -gt $LIMIT ] || break + case "$NAME" in + /var/log/messages.1|/var/log/messages.1.gz) continue ;; + esac + logger "syslog message total size exceeded the limit. Removing retentions." + rm -f $NAME + total=`du -c /var/log/messages* |grep total |awk '{print $1}'` done diff --git a/rootfs/Makefile b/rootfs/Makefile index 0a13994..e5e5021 100755 --- a/rootfs/Makefile +++ b/rootfs/Makefile @@ -164,7 +164,6 @@ full_rootfs_install:: $(Q)sed -i -e "s/.*PermitRootLogin .*/PermitRootLogin yes/" -e "s/.*PasswordAuthentication .*/PasswordAuthentication yes/" $(ROOTDIR)/etc/ssh/sshd_config $(Q)ln -sf /lib/systemd/system/systemd-hexctl-user.service $(ROOTDIR)/etc/systemd/system/multi-user.target.wants/systemd-hexctl-user.service $(Q)chroot $(ROOTDIR) systemctl disable crond sshd - $(Q)chroot $(ROOTDIR) mv /etc/cron.daily/logrotate /etc/cron.hourly/ || true $(Q)chroot $(ROOTDIR) mv /etc/logrotate.d/rsyslog /etc/logrotate.d/rsyslog.disabled $(Q)chroot $(ROOTDIR) bash -c "rm -rf /usr/local/share/* /usr/share/{man,doc,licenses} /usr/src /usr/local/src /var/{log,cache}/* /tmp/* /lib/.build-id" diff --git a/src/hex_sdk_library/logrotate/logrotate.cpp b/src/hex_sdk_library/logrotate/logrotate.cpp index 19dee7e..e3a6616 100644 --- a/src/hex_sdk_library/logrotate/logrotate.cpp +++ b/src/hex_sdk_library/logrotate/logrotate.cpp @@ -18,9 +18,34 @@ WriteDefLogRotateConf(int retention = 4) return false; } + // retention is a number of DAYS -- that is what cubesys.log.default.retention + // promises operators ("Set log file retention policy in days") and what the + // audit expectation is built on. logrotate's `rotate` counts GENERATIONS, so + // on its own it only ever approximates that promise, and the approximation + // holds exactly while a log rotates once per day and no more. + // + // The two bounds do different work, which is why both are emitted: + // + // rotate caps the number of files a busy log can leave behind. On the + // shipped daily schedule that is also its age, one generation per + // day. + // maxage caps their age directly, and is the only one of the two that says + // anything about a log that rotates *rarely*. `notifempty` and a + // quiet service mean generations can sit for months and still be + // well inside a count of 14 -- without maxage they are kept + // indefinitely, long past the window the tuning advertises. + // + // What neither covers, and is worth knowing rather than papering over: if + // something rotates a log more than once in a day -- an operator's + // `logrotate -f`, a fixpack or enabler driving a rotation -- `rotate` spends a + // generation without spending a day and the window shrinks below the + // advertised days. That is long-standing behaviour and this does not change + // it; sizing `rotate` past the count needed for a daily schedule would, at the + // cost of keeping far more files than the schedule can ever produce. fprintf(fout, "daily\n"); fprintf(fout, "su root syslog\n"); fprintf(fout, "rotate %d\n", retention); + fprintf(fout, "maxage %d\n", retention); fprintf(fout, "create\n"); fprintf(fout, "include /etc/logrotate.d\n");