1#!/bin/sh 2 3# NAME: 4# newlog - rotate log files 5# 6# SYNOPSIS: 7# newlog.sh [options] "log"[:"num"] ... 8# 9# DESCRIPTION: 10# This script saves multiple generations of each "log". 11# The "logs" are kept compressed except for the current and 12# previous ones. 13# 14# Options: 15# 16# -C "compress" 17# Compact old logs (other than .0) with "compress" 18# (default is 'gzip' or 'compress' if no 'gzip'). 19# 20# -E "ext" 21# If "compress" produces a file extention other than 22# '.Z' or '.gz' we need to know. 23# 24# -G "gens" 25# "gens" is a comma separated list of "log":"num" pairs 26# that allows certain logs to handled differently. 27# 28# -N Don't actually do anything, just show us. 29# 30# -R Rotate rather than save logs by default. 31# This is the default anyway. 32# 33# -S Save rather than rotate logs by default. 34# Each log is saved to a unique name that remains 35# unchanged. This results in far less churn. 36# 37# -f "fmt" 38# Format ('%Y%m%d.%H%M%S') for suffix added to "log" to 39# uniquely name it when using the '-S' option. 40# If a "log" is saved more than once per second we add 41# an extra suffix of our process-id. 42# 43# -d The "log" to be rotated/saved is a directory. 44# We leave the mode of old directories alone. 45# 46# -e Normally logs are only cycled if non-empty, this 47# option forces empty logs to be cycled as well. 48# 49# -g "group" 50# Set the group of "log" to "group". 51# 52# -m "mode" 53# Set the mode of "log". 54# 55# -M "mode" 56# Set the mode of old logs (default 444). 57# 58# -n "num" 59# Keep "num" generations of "log". 60# 61# -o "owner" 62# Set the owner of "log". 63# 64# Regardless of whether '-R' or '-S' is provided, we attempt to 65# choose the correct behavior based on observation of "log.0" if 66# it exists; if it is a symbolic link, we save, otherwise 67# we rotate. 68# 69# BUGS: 70# 'Newlog.sh' tries to avoid being fooled by symbolic links, but 71# multiply indirect symlinks are only handled on machines where 72# test(1) supports a check for symlinks. 73# 74# AUTHOR: 75# Simon J. Gerraty <sjg@crufty.net> 76# 77 78# RCSid: 79# $Id: newlog.sh,v 1.27 2024/02/17 17:26:57 sjg Exp $ 80# 81# SPDX-License-Identifier: BSD-2-Clause 82# 83# @(#) Copyright (c) 1993-2016 Simon J. Gerraty 84# 85# This file is provided in the hope that it will 86# be of use. There is absolutely NO WARRANTY. 87# Permission to copy, redistribute or otherwise 88# use this file is hereby granted provided that 89# the above copyright notice and this notice are 90# left intact. 91# 92# Please send copies of changes and bug-fixes to: 93# sjg@crufty.net 94# 95 96Mydir=`dirname $0` 97case $Mydir in 98/*) ;; 99*) Mydir=`cd $Mydir; pwd`;; 100esac 101 102# places to find chown (and setopts.sh) 103PATH=$PATH:/usr/etc:/sbin:/usr/sbin:/usr/local/share/bin:/share/bin:$Mydir 104 105# linux doesn't necessarily have compress, 106# and gzip appears in various locations... 107Which() { 108 case "$1" in 109 -*) t=$1; shift;; 110 *) t=-x;; 111 esac 112 case "$1" in 113 /*) test $t $1 && echo $1;; 114 *) 115 for d in `IFS=:; echo ${2:-$PATH}` 116 do 117 test $t $d/$1 && { echo $d/$1; break; } 118 done 119 ;; 120 esac 121} 122 123# shell's typically have test(1) as built-in 124# and not all support all options. 125test_opt() { 126 _o=$1 127 _a=$2 128 _t=${3:-/} 129 130 case `test -$_o $_t 2>&1` in 131 *:*) eval test_$_o=$_a;; 132 *) eval test_$_o=-$_o;; 133 esac 134} 135 136# convert find/ls mode to octal 137fmode() { 138 eval `echo $1 | 139 sed 's,\(.\)\(...\)\(...\)\(...\),ft=\1 um=\2 gm=\3 om=\4,'` 140 sm= 141 case "$um" in 142 *s*) sm=r 143 um=`echo $um | sed 's,s,x,'` 144 ;; 145 *) sm=-;; 146 esac 147 case "$gm" in 148 *[Ss]*) 149 sm=${sm}w 150 gm=`echo $gm | sed 's,s,x,;s,S,-,'` 151 ;; 152 *) sm=${sm}-;; 153 esac 154 case "$om" in 155 *t) 156 sm=${sm}x 157 om=`echo $om | sed 's,t,x,'` 158 ;; 159 *) sm=${sm}-;; 160 esac 161 echo $sm $um $gm $om | 162 sed 's,rwx,7,g;s,rw-,6,g;s,r-x,5,g;s,r--,4,g;s,-wx,3,g;s,-w-,2,g;s,--x,1,g;s,---,0,g;s, ,,g' 163} 164 165get_mode() { 166 case "$OS,$STAT" in 167 FreeBSD,*) 168 $STAT -f %Op $1 | sed 's,.*\(....\),\1,' 169 return 170 ;; 171 esac 172 # fallback to find 173 fmode `find $1 -ls -prune | awk '{ print $3 }'` 174} 175 176get_mtime_suffix() { 177 case "$OS,$STAT" in 178 FreeBSD,*) 179 $STAT -t "${2:-$opt_f}" -f %Sm $1 180 return 181 ;; 182 esac 183 # this will have to do 184 date "+${2:-$opt_f}" 185} 186 187case /$0 in 188*/newlog*) rotate_func=rotate_log;; 189*/save*) rotate_func=save_log;; 190*) rotate_func=rotate_log;; 191esac 192 193opt_n=7 194opt_m= 195opt_M=444 196opt_f=%Y%m%d.%H%M%S 197opt_str=dNn:o:g:G:C:M:m:eE:f:RS 198 199. setopts.sh 200 201test $# -gt 0 || exit 0 # nothing to do. 202 203OS=${OS:-`uname`} 204STAT=${STAT:-`Which stat`} 205 206# sorry, setops semantics for booleans changed. 207case "${opt_d:-0}" in 2080) rm_f=-f 209 opt_d=-f 210 for x in $opt_C gzip compress 211 do 212 opt_C=`Which $x "/bin:/usr/bin:$PATH"` 213 test -x $opt_C && break 214 done 215 empty() { test ! -s $1; } 216 ;; 217*) rm_f=-rf 218 opt_d=-d 219 opt_M= 220 opt_C=: 221 empty() { 222 if [ -d $1 ]; then 223 n=`'ls' -a1 $1/. | wc -l` 224 [ $n -gt 2 ] && return 1 225 fi 226 return 0 227 } 228 ;; 229esac 230case "${opt_N:-0}" in 2310) ECHO=;; 232*) ECHO=echo;; 233esac 234case "${opt_e:-0}" in 2350) force=;; 236*) force=yes;; 237esac 238case "${opt_R:-0}" in 2390) ;; 240*) rotate_func=rotate_log;; 241esac 242case "${opt_S:-0}" in 2430) ;; 244*) rotate_func=save_log opt_S=;; 245esac 246 247# see whether test handles -h or -L 248test_opt L -h 249test_opt h "" 250case "$test_L,$test_h" in 251-h,) test_L= ;; # we don't support either! 252esac 253 254case "$test_L" in 255"") # No, so this is about all we can do... 256 logs=`'ls' -ld $* | awk '{ print $NF }'` 257 ;; 258*) # it does 259 logs="$*" 260 ;; 261esac 262 263read_link() { 264 case "$test_L" in 265 "") 'ls' -ld $1 | awk '{ print $NF }'; return;; 266 esac 267 if test $test_L $1; then 268 'ls' -ld $1 | sed 's,.*> ,,' 269 else 270 echo $1 271 fi 272} 273 274# create the new log 275new_log() { 276 log=$1 277 mode=$2 278 if test "x$opt_M" != x; then 279 $ECHO chmod $opt_M $log.0 2> /dev/null 280 fi 281 # someone may have managed to write to it already 282 # so don't truncate it. 283 case "$opt_d" in 284 -d) $ECHO mkdir -p $log;; 285 *) $ECHO touch $log;; 286 esac 287 # the order here matters 288 test "x$opt_o" = x || $ECHO chown $opt_o $log 289 test "x$opt_g" = x || $ECHO chgrp $opt_g $log 290 test "x$mode" = x || $ECHO chmod $mode $log 291} 292 293rotate_log() { 294 log=$1 295 n=${2:-$opt_n} 296 297 # make sure excess generations are trimmed 298 $ECHO rm $rm_f `echo $log.$n | sed 's/\([0-9]\)$/[\1-9]*/'` 299 300 mode=${opt_m:-`get_mode $log`} 301 while test $n -gt 0 302 do 303 p=`expr $n - 1` 304 if test -s $log.$p; then 305 $ECHO rm $rm_f $log.$p.* 306 $ECHO $opt_C $log.$p 307 if test "x$opt_M" != x; then 308 $ECHO chmod $opt_M $log.$p.* 2> /dev/null 309 fi 310 fi 311 for ext in $opt_E .gz .Z "" 312 do 313 test $opt_d $log.$p$ext || continue 314 $ECHO mv $log.$p$ext $log.$n$ext 315 done 316 n=$p 317 done 318 # leave $log.0 uncompressed incase some one still has it open. 319 $ECHO mv $log $log.0 320 new_log $log $mode 321} 322 323# unlike rotate_log we do not rotate files, 324# but give each log a unique (but stable name). 325# This avoids churn for folk who rsync things. 326# We make log.0 a symlink to the most recent log 327# so it can be found and compressed next time around. 328save_log() { 329 log=$1 330 n=${2:-$opt_n} 331 fmt=$3 332 333 last=`read_link $log.0` 334 case "$last" in 335 $log.0) # should never happen 336 test -s $last && $ECHO mv $last $log.$$;; 337 $log.*) 338 $ECHO $opt_C $last 339 ;; 340 *.*) $ECHO $opt_C `dirname $log`/$last 341 ;; 342 esac 343 $ECHO rm -f $log.0 344 # remove excess logs - we rely on mtime! 345 $ECHO rm $rm_f `'ls' -1td $log.* 2> /dev/null | sed "1,${n}d"` 346 347 mode=${opt_m:-`get_mode $log`} 348 # this is our default suffix 349 opt_S=${opt_S:-`get_mtime_suffix $log $fmt`} 350 case "$fmt" in 351 ""|$opt_f) suffix=$opt_S;; 352 *) suffix=`get_mtime_suffix $log $fmt`;; 353 esac 354 355 # find a unique name to save current log as 356 for nlog in $log.$suffix $log.$suffix.$$ 357 do 358 for f in $nlog* 359 do 360 break 361 done 362 test $opt_d $f || break 363 done 364 # leave $log.0 uncompressed incase some one still has it open. 365 $ECHO mv $log $nlog 366 test "x$opt_M" = x || $ECHO chmod $opt_M $nlog 2> /dev/null 367 $ECHO ln -s `basename $nlog` $log.0 368 new_log $log $mode 369} 370 371for f in $logs 372do 373 n=$opt_n 374 save= 375 case "$f" in 376 *:[1-9]*) 377 set -- `IFS=:; echo $f`; f=$1; n=$2;; 378 *:n=*|*:save=*) 379 eval `echo "f=$f" | tr ':' ' '`;; 380 esac 381 # try and pick the right function to use 382 rfunc=$rotate_func # default 383 if test $opt_d $f.0; then 384 case `read_link $f.0` in 385 $f.0) rfunc=rotate_log;; 386 *) rfunc=save_log;; 387 esac 388 fi 389 case "$test_L" in 390 -?) 391 while test $test_L $f # it is [still] a symlink 392 do 393 f=`read_link $f` 394 done 395 ;; 396 esac 397 case ",${opt_G}," in 398 *,${f}:n=*|,${f}:save=*) 399 eval `echo ",${opt_G}," | sed "s!.*,${f}:\([^,]*\),.*!\1!;s,:, ,g"` 400 ;; 401 *,${f}:*) 402 # opt_G is a , separated list of log:n pairs 403 n=`echo ,$opt_G, | sed -e "s,.*${f}:\([0-9][0-9]*\).*,\1,"` 404 ;; 405 esac 406 407 if empty $f; then 408 test "$force" || continue 409 fi 410 411 test "$save" && rfunc=save_log 412 413 $rfunc $f $n $save 414done 415