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.26 2021/04/30 16:29:02 sjg Exp $ 80# 81# @(#) Copyright (c) 1993-2016 Simon J. Gerraty 82# 83# This file is provided in the hope that it will 84# be of use. There is absolutely NO WARRANTY. 85# Permission to copy, redistribute or otherwise 86# use this file is hereby granted provided that 87# the above copyright notice and this notice are 88# left intact. 89# 90# Please send copies of changes and bug-fixes to: 91# sjg@crufty.net 92# 93 94Mydir=`dirname $0` 95case $Mydir in 96/*) ;; 97*) Mydir=`cd $Mydir; pwd`;; 98esac 99 100# places to find chown (and setopts.sh) 101PATH=$PATH:/usr/etc:/sbin:/usr/sbin:/usr/local/share/bin:/share/bin:$Mydir 102 103# linux doesn't necessarily have compress, 104# and gzip appears in various locations... 105Which() { 106 case "$1" in 107 -*) t=$1; shift;; 108 *) t=-x;; 109 esac 110 case "$1" in 111 /*) test $t $1 && echo $1;; 112 *) 113 for d in `IFS=:; echo ${2:-$PATH}` 114 do 115 test $t $d/$1 && { echo $d/$1; break; } 116 done 117 ;; 118 esac 119} 120 121# shell's typically have test(1) as built-in 122# and not all support all options. 123test_opt() { 124 _o=$1 125 _a=$2 126 _t=${3:-/} 127 128 case `test -$_o $_t 2>&1` in 129 *:*) eval test_$_o=$_a;; 130 *) eval test_$_o=-$_o;; 131 esac 132} 133 134# convert find/ls mode to octal 135fmode() { 136 eval `echo $1 | 137 sed 's,\(.\)\(...\)\(...\)\(...\),ft=\1 um=\2 gm=\3 om=\4,'` 138 sm= 139 case "$um" in 140 *s*) sm=r 141 um=`echo $um | sed 's,s,x,'` 142 ;; 143 *) sm=-;; 144 esac 145 case "$gm" in 146 *[Ss]*) 147 sm=${sm}w 148 gm=`echo $gm | sed 's,s,x,;s,S,-,'` 149 ;; 150 *) sm=${sm}-;; 151 esac 152 case "$om" in 153 *t) 154 sm=${sm}x 155 om=`echo $om | sed 's,t,x,'` 156 ;; 157 *) sm=${sm}-;; 158 esac 159 echo $sm $um $gm $om | 160 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' 161} 162 163get_mode() { 164 case "$OS,$STAT" in 165 FreeBSD,*) 166 $STAT -f %Op $1 | sed 's,.*\(....\),\1,' 167 return 168 ;; 169 esac 170 # fallback to find 171 fmode `find $1 -ls -prune | awk '{ print $3 }'` 172} 173 174get_mtime_suffix() { 175 case "$OS,$STAT" in 176 FreeBSD,*) 177 $STAT -t "${2:-$opt_f}" -f %Sm $1 178 return 179 ;; 180 esac 181 # this will have to do 182 date "+${2:-$opt_f}" 183} 184 185case /$0 in 186*/newlog*) rotate_func=rotate_log;; 187*/save*) rotate_func=save_log;; 188*) rotate_func=rotate_log;; 189esac 190 191opt_n=7 192opt_m= 193opt_M=444 194opt_f=%Y%m%d.%H%M%S 195opt_str=dNn:o:g:G:C:M:m:eE:f:RS 196 197. setopts.sh 198 199test $# -gt 0 || exit 0 # nothing to do. 200 201OS=${OS:-`uname`} 202STAT=${STAT:-`Which stat`} 203 204# sorry, setops semantics for booleans changed. 205case "${opt_d:-0}" in 2060) rm_f=-f 207 opt_d=-f 208 for x in $opt_C gzip compress 209 do 210 opt_C=`Which $x "/bin:/usr/bin:$PATH"` 211 test -x $opt_C && break 212 done 213 empty() { test ! -s $1; } 214 ;; 215*) rm_f=-rf 216 opt_d=-d 217 opt_M= 218 opt_C=: 219 empty() { 220 if [ -d $1 ]; then 221 n=`'ls' -a1 $1/. | wc -l` 222 [ $n -gt 2 ] && return 1 223 fi 224 return 0 225 } 226 ;; 227esac 228case "${opt_N:-0}" in 2290) ECHO=;; 230*) ECHO=echo;; 231esac 232case "${opt_e:-0}" in 2330) force=;; 234*) force=yes;; 235esac 236case "${opt_R:-0}" in 2370) ;; 238*) rotate_func=rotate_log;; 239esac 240case "${opt_S:-0}" in 2410) ;; 242*) rotate_func=save_log opt_S=;; 243esac 244 245# see whether test handles -h or -L 246test_opt L -h 247test_opt h "" 248case "$test_L,$test_h" in 249-h,) test_L= ;; # we don't support either! 250esac 251 252case "$test_L" in 253"") # No, so this is about all we can do... 254 logs=`'ls' -ld $* | awk '{ print $NF }'` 255 ;; 256*) # it does 257 logs="$*" 258 ;; 259esac 260 261read_link() { 262 case "$test_L" in 263 "") 'ls' -ld $1 | awk '{ print $NF }'; return;; 264 esac 265 if test $test_L $1; then 266 'ls' -ld $1 | sed 's,.*> ,,' 267 else 268 echo $1 269 fi 270} 271 272# create the new log 273new_log() { 274 log=$1 275 mode=$2 276 if test "x$opt_M" != x; then 277 $ECHO chmod $opt_M $log.0 2> /dev/null 278 fi 279 # someone may have managed to write to it already 280 # so don't truncate it. 281 case "$opt_d" in 282 -d) $ECHO mkdir -p $log;; 283 *) $ECHO touch $log;; 284 esac 285 # the order here matters 286 test "x$opt_o" = x || $ECHO chown $opt_o $log 287 test "x$opt_g" = x || $ECHO chgrp $opt_g $log 288 test "x$mode" = x || $ECHO chmod $mode $log 289} 290 291rotate_log() { 292 log=$1 293 n=${2:-$opt_n} 294 295 # make sure excess generations are trimmed 296 $ECHO rm $rm_f `echo $log.$n | sed 's/\([0-9]\)$/[\1-9]*/'` 297 298 mode=${opt_m:-`get_mode $log`} 299 while test $n -gt 0 300 do 301 p=`expr $n - 1` 302 if test -s $log.$p; then 303 $ECHO rm $rm_f $log.$p.* 304 $ECHO $opt_C $log.$p 305 if test "x$opt_M" != x; then 306 $ECHO chmod $opt_M $log.$p.* 2> /dev/null 307 fi 308 fi 309 for ext in $opt_E .gz .Z "" 310 do 311 test $opt_d $log.$p$ext || continue 312 $ECHO mv $log.$p$ext $log.$n$ext 313 done 314 n=$p 315 done 316 # leave $log.0 uncompressed incase some one still has it open. 317 $ECHO mv $log $log.0 318 new_log $log $mode 319} 320 321# unlike rotate_log we do not rotate files, 322# but give each log a unique (but stable name). 323# This avoids churn for folk who rsync things. 324# We make log.0 a symlink to the most recent log 325# so it can be found and compressed next time around. 326save_log() { 327 log=$1 328 n=${2:-$opt_n} 329 fmt=$3 330 331 last=`read_link $log.0` 332 case "$last" in 333 $log.0) # should never happen 334 test -s $last && $ECHO mv $last $log.$$;; 335 $log.*) 336 $ECHO $opt_C $last 337 ;; 338 *.*) $ECHO $opt_C `dirname $log`/$last 339 ;; 340 esac 341 $ECHO rm -f $log.0 342 # remove excess logs - we rely on mtime! 343 $ECHO rm $rm_f `'ls' -1td $log.* 2> /dev/null | sed "1,${n}d"` 344 345 mode=${opt_m:-`get_mode $log`} 346 # this is our default suffix 347 opt_S=${opt_S:-`get_mtime_suffix $log $fmt`} 348 case "$fmt" in 349 ""|$opt_f) suffix=$opt_S;; 350 *) suffix=`get_mtime_suffix $log $fmt`;; 351 esac 352 353 # find a unique name to save current log as 354 for nlog in $log.$suffix $log.$suffix.$$ 355 do 356 for f in $nlog* 357 do 358 break 359 done 360 test $opt_d $f || break 361 done 362 # leave $log.0 uncompressed incase some one still has it open. 363 $ECHO mv $log $nlog 364 test "x$opt_M" = x || $ECHO chmod $opt_M $nlog 2> /dev/null 365 $ECHO ln -s `basename $nlog` $log.0 366 new_log $log $mode 367} 368 369for f in $logs 370do 371 n=$opt_n 372 save= 373 case "$f" in 374 *:[1-9]*) 375 set -- `IFS=:; echo $f`; f=$1; n=$2;; 376 *:n=*|*:save=*) 377 eval `echo "f=$f" | tr ':' ' '`;; 378 esac 379 # try and pick the right function to use 380 rfunc=$rotate_func # default 381 if test $opt_d $f.0; then 382 case `read_link $f.0` in 383 $f.0) rfunc=rotate_log;; 384 *) rfunc=save_log;; 385 esac 386 fi 387 case "$test_L" in 388 -?) 389 while test $test_L $f # it is [still] a symlink 390 do 391 f=`read_link $f` 392 done 393 ;; 394 esac 395 case ",${opt_G}," in 396 *,${f}:n=*|,${f}:save=*) 397 eval `echo ",${opt_G}," | sed "s!.*,${f}:\([^,]*\),.*!\1!;s,:, ,g"` 398 ;; 399 *,${f}:*) 400 # opt_G is a , separated list of log:n pairs 401 n=`echo ,$opt_G, | sed -e "s,.*${f}:\([0-9][0-9]*\).*,\1,"` 402 ;; 403 esac 404 405 if empty $f; then 406 test "$force" || continue 407 fi 408 409 test "$save" && rfunc=save_log 410 411 $rfunc $f $n $save 412done 413