File Coverage

blib/lib/Logfile/Rotate.pm
Criterion Covered Total %
statement 120 124 96.7
branch 58 92 63.0
condition 17 21 80.9
subroutine 15 15 100.0
pod 2 2 100.0
total 212 254 83.4


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2             ###############################################################################
3             #
4             # $Id: Rotate.pm,v 1.5 2000/08/29 03:57:23 paulg Exp $ vim:ts=4
5             #
6             # Copyright (c) 1997-99 Paul Gampe. All Rights Reserved.
7             #
8             # This program is free software; you can redistribute it and/or modify it
9             # under the same terms as Perl itself. See COPYRIGHT section below.
10             #
11             ###############################################################################
12              
13             ###############################################################################
14             ## L I B R A R I E S / M O D U L E S
15             ###############################################################################
16              
17             package Logfile::Rotate;
18              
19 9     9   6541 use Config; # do we have gzip
  9         20  
  9         412  
20 9     9   52 use Carp;
  9         19  
  9         804  
21 9     9   8983 use IO::File;
  9         112958  
  9         1285  
22 9     9   9298 use File::Copy;
  9         50728  
  9         666  
23 9     9   69 use Fcntl qw(:flock);
  9         15  
  9         1380  
24              
25 9     9   49 use strict;
  9         19  
  9         378  
26              
27             ###############################################################################
28             ## G L O B A L V A R I A B L E S
29             ###############################################################################
30              
31 9     9   53 use vars qw($VERSION $COUNT $GZIP_FLAG);
  9         14  
  9         16231  
32              
33             $VERSION = do { my @r=(q$Revision: 1.5 $=~/\d+/g); sprintf "%d."."%02d"x$#r,@r};
34             $COUNT =7; # default to keep 7 copies
35             $GZIP_FLAG='-qf'; # force writing over old logfiles
36              
37             ###############################################################################
38             ## E X P O R T S
39             ###############################################################################
40              
41             ###############################################################################
42             ## M A I N
43             ###############################################################################
44              
45             sub new {
46 13     13 1 77633 my ($class, %args) = @_;
47              
48 13 50       68 croak("usage: new( File => filename
49             [, Count => cnt ]
50             [, Gzip => lib or \"/path/to/gzip\" or no ]
51             [, Signal => \&sub_signal ]
52             [, Pre => \&sub_pre ]
53             [, Post => \&sub_post ]
54             [, Flock => yes or no ]
55             [, Persist => yes or no ]
56             [, Dir => \"dir/to/put/old/files/into\"] )")
57             unless defined($args{'File'});
58              
59 13         35 my $self = {};
60 13         44 $self->{'Fh'} = undef;
61 13         38 $self->{'File'} = $args{'File'};
62 13   50     59 $self->{'Count'} = ($args{'Count'} or 7);
63 13   100 36   154 $self->{'Signal'} = ($args{'Signal'} or sub {1;});
  36         59  
64 13   100 33   109 $self->{'Pre'} = ($args{'Pre'} or sub {1;});
  33         52  
65 13   100 33   106 $self->{'Post'} = ($args{'Post'} or sub {1;});
  33         44  
66 13   100     83 $self->{'Flock'} = ($args{'Flock'} or 'yes');
67 13   100     89 $self->{'Persist'} = ($args{'Persist'} or 'yes');
68              
69             # deprecated methods
70 13 100       362 carp "Signal is a deprecated argument, see Pre/Post" if $args{'Signal'};
71              
72             # mutual excl
73 13 50 66     277 croak "Can not define both Signal and Post"
74             if ($args{Signal} and $args{Post});
75              
76 13 50       59 (ref($self->{'Signal'}) eq "CODE")
77             or croak "error: Signal is not a CODE reference.";
78              
79 13 50       50 (ref($self->{'Pre'}) eq "CODE")
80             or croak "error: Pre is not a CODE reference.";
81              
82 13 50       45 (ref($self->{'Post'}) eq "CODE")
83             or croak "error: Post is not a CODE reference.";
84              
85             # Process compression arg
86 13 100       44 unless ($args{Gzip}) {
87 1 50       3 if (_have_compress_zlib()) {
88 1         5 $self->{Gzip} = 'lib';
89             } else {
90 0         0 $self->{Gzip} = $Config{gzip};
91             }
92             } else {
93 12 100       40 if ($args{Gzip} eq 'no') {
94 11         23 $self->{Gzip} = undef;
95             } else {
96 1         4 $self->{Gzip} = $args{Gzip};
97             }
98             }
99              
100              
101             # Process directory arg
102              
103 13 100       41 if (defined($args{'Dir'})) {
104 1         2 $self->{'Dir'} = $args{'Dir'};
105             # don't know about creating directories ??
106 1 50       84 mkdir($self->{'Dir'},0750) unless (-d $self->{'Dir'});
107             } else {
108 12         28 $self->{'Dir'} = undef;
109             }
110              
111             # confirm existence of dir
112              
113 13 100       45 if (defined $self->{'Dir'} ) {
114 1 50       37 croak "error: $self->{'Dir'} not writable"
115             unless (-w $self->{'Dir'});
116 1 50       12 croak "error: $self->{'Dir'} not executable"
117             unless (-x $self->{'Dir'});
118             }
119              
120             # open and lock the file
121 13 100       47 if( $self->{'Flock'} eq 'yes'){
122 12         113 $self->{'Fh'} = new IO::File "$self->{'File'}", O_WRONLY|O_EXCL;
123 12 50       1188 croak "error: can not lock open: ($self->{'File'})"
124             unless defined($self->{'Fh'});
125 12         109 flock($self->{'Fh'},LOCK_EX);
126             }
127             else{
128 1         12 $self->{'Fh'} = new IO::File "$self->{'File'}";
129 1 50       99 croak "error: can not open: ($self->{'File'})"
130             unless defined($self->{'Fh'});
131             }
132              
133 13         63 bless $self, $class;
134             }
135              
136             sub rotate {
137 39     39 1 15548 my ($self, %args) = @_;
138              
139 39         84 my ($prev,$next,$i,$j);
140              
141             # check we still have a filehandle
142 39 50       132 croak "error: lost file handle, may have called rotate twice ?"
143             unless defined($self->{'Fh'});
144              
145 39         80 my $curr = $self->{'File'};
146 39         160 my $currn = $curr;
147 39 100       123 my $ext = $self->{'Gzip'} ? '.gz' : '';
148              
149             # Execute and exit if Pre method fails
150 39 50       107 eval { &{$self->{'Pre'}}($curr); } if $self->{Pre};
  39         67  
  39         96  
151 39 50       890 croak "error: your supplied Pre function failed: $@" if ($@);
152              
153             # TODO: what is this doing ??
154 39 100       122 my $dir = defined($self->{'Dir'}) ? "$self->{'Dir'}/" : "";
155 39 100       147 $currn =~ s+.*/([^/]*)+$self->{'Dir'}/$1+ if defined($self->{'Dir'});
156              
157 39         133 for($i = $self->{'Count'}; $i > 1; $i--) {
158 78         1195 $j = $i - 1;
159 78         173 $next = "${currn}." . $i . $ext;
160 78         162 $prev = "${currn}." . $j . $ext;
161 78 100 66     1643 if ( -r $prev && -f $prev ) {
162 39 50       697 move($prev,$next) ## move will attempt rename for us
163             or croak "error: move failed: ($prev,$next)";
164             }
165             }
166              
167             ## copy current to next incremental
168 39         2257 $next = "${currn}.1";
169 39         150 copy ($curr, $next);
170              
171             ## preserve permissions and status
172 39 100       9917 if ( $self->{'Persist'} eq 'yes' ){
173 36         546 my @stat = stat $curr;
174 36 50       907 chmod( $stat[2], $next ) or carp "error: chmod failed: ($next)";
175 36 50       650 utime( $stat[8], $stat[9], $next ) or carp "error: failed: ($next)";
176 36 50       713 chown( $stat[4], $stat[5], $next ) or carp "error: chown failed: ($next)";
177             }
178              
179             # now truncate the file
180 39 100       134 if( $self->{'Flock'} eq 'yes' )
181             {
182 36 50       1646 truncate $curr,0 or croak "error: could not truncate $curr: $!"; }
183             else{
184 3         7 local(*IN);
185 3 50       238 open(IN, "+>$self->{'File'}")
186             or croak "error: could not truncate $curr: $!";
187             }
188              
189 39 100 66     203 if ($self->{'Gzip'} and $self->{'Gzip'} eq 'lib')
    50          
190             {
191 6         26 _gzip($next, $next.$ext);
192             }
193             elsif ($self->{'Gzip'})
194             {
195             # WARNING: may not be safe system call
196 0 0       0 ( 0 == (system $self->{'Gzip'}, $GZIP_FLAG, $next) )
197             or croak "error: ", $self->{'Gzip'}, " failed";
198             }
199              
200             # TODO: deprecated: remove next release
201 39 50       510 eval { &{$self->{'Signal'}}($curr, $next); } if ($self->{Signal});
  39         65  
  39         118  
202 39 50       104 croak "error: your supplied Signal function failed: $@" if ($@);
203              
204             # Execute and exit on post method
205 39 50       106 eval { &{$self->{'Post'}}($curr, $next); } if $self->{Post};
  39         55  
  39         564  
206 39 50       1434 croak "error: your supplied Post function failed: $@" if ($@);
207              
208             # if we made it here we have succeeded
209 39         195 return 1;
210             }
211              
212             sub DESTROY {
213 4     4   29 my ($self, %args) = @_;
214 4 50       16 return unless $self->{'Fh'}; # already gone
215 4         25 flock($self->{'Fh'},LOCK_UN);
216 4         1397 undef $self->{'Fh'}; # auto-close
217             }
218              
219             sub _have_compress_zlib {
220             # try and load the compression library
221 7     7   14 eval { require Compress::Zlib; };
  7         2287  
222 7 50       82802 if ($@) {
223 0         0 carp "warning: could not load Compress::Zlib, skipping compression" ;
224 0         0 return undef;
225             }
226 7         21 return 1;
227             }
228              
229             sub _gzip {
230 6     6   9 my $in = shift;
231 6         10 my $out = shift;
232              
233             # ASSERT
234 6 50       12 croak "error: _gzip called without mandatory argument" unless $in;
235              
236 6 50       16 return unless _have_compress_zlib();
237              
238 6         9 my($buffer,$fhw);
239 6 50       34 $fhw = new IO::File $in
240             or croak "error: could not open $in: $!";
241 6 50       438 my $gz = Compress::Zlib::gzopen($out, "wb")
242             or croak "error: could not gzopen $out: $!";
243 6         8823 $gz->gzwrite($buffer)
244             while read($fhw,$buffer,4096) > 0 ;
245 6         1143 $gz->gzclose() ;
246 6         1419 $fhw->close;
247              
248 6 50       527 unlink $in or croak "error: could not delete $in: $!";
249              
250 6         48 return 1;
251             }
252              
253             1;
254              
255              
256             __END__