1 #============================================================= -*-perl-*-
3 # BackupPC::FileZIO package
7 # This library defines a BackupPC::FileZIO class for doing
8 # compressed or normal file I/O.
11 # Craig Barratt <cbarratt@users.sourceforge.net>
14 # Copyright (C) 2001-2009 Craig Barratt
16 # This program is free software; you can redistribute it and/or modify
17 # it under the terms of the GNU General Public License as published by
18 # the Free Software Foundation; either version 2 of the License, or
19 # (at your option) any later version.
21 # This program is distributed in the hope that it will be useful,
22 # but WITHOUT ANY WARRANTY; without even the implied warranty of
23 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 # GNU General Public License for more details.
26 # You should have received a copy of the GNU General Public License
27 # along with this program; if not, write to the Free Software
28 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
30 #========================================================================
32 # Version 3.2.0, released 31 Jul 2010.
34 # See http://backuppc.sourceforge.net.
36 #========================================================================
38 package BackupPC::FileZIO;
42 use vars qw( $CompZlibOK );
49 # For compressed files we have a to careful about running out of memory
50 # when we inflate a deflated file. For example, if a 500MB file of all
51 # zero-bytes is compressed, it will only occupy a few tens of kbytes. If
52 # we read the compressed file in decent-size chunks, a single inflate
53 # will try to allocate 500MB. Not a good idea.
55 # Instead, we compress the file in chunks of $CompMaxWrite. If a
56 # deflated chunk produces less than $CompMaxRead bytes, then we flush
57 # and continue. This adds a few bytes to the compressed output file, but
58 # only in extreme cases where the compression ratio is very close to
59 # 100%. The result is that, provided we read the compressed file in
60 # chunks of $CompMaxRead or less, the biggest inflated data will be
63 my $CompMaxRead = 131072; # 128K
64 my $CompMaxWrite = 6291456; # 6MB
67 # We maintain a write buffer for small writes for both compressed and
68 # uncompressed files. This is the size of the write buffer.
70 my $WriteBufSize = 65536;
73 eval "use Compress::Zlib;";
76 # Compress::Zlib doesn't exist. Define some dummy constant
77 # subs so that the code below doesn't barf.
80 sub Z_OK { return 0; }
81 sub Z_STREAM_END { return 1; }
91 my($class, $fileName, $write, $compLevel) = @_;
95 if ( ref(\$fileName) eq "GLOB" ) {
99 open(FH, ">", $fileName) || return;
101 open(FH, "<", $fileName) || return;
106 $compLevel = 0 if ( !$CompZlibOK );
112 compress => $compLevel,
116 $self->{deflate} = $self->myDeflateInit;
118 $self->{inflate} = $self->myInflateInit;
119 $self->{inflateStart} = 1;
131 # Request utf8 strings with readLine interface
135 my($self, $mode) = @_;
137 $self->{utf8} = $mode;
146 -Level => $self->{compress},
161 my($self, $dataRef, $nRead) = @_;
164 return if ( $self->{write} );
165 return sysread($self->{fh}, $$dataRef, $nRead) if ( !$self->{compress} );
166 while ( !$self->{eof} && $nRead > length($self->{dataOut}) ) {
167 if ( !length($self->{dataIn}) ) {
168 $n = sysread($self->{fh}, $self->{dataIn}, $CompMaxRead);
169 return $n if ( $n < 0 );
170 $self->{eof} = 1 if ( $n == 0 );
172 if ( $self->{inflateStart} && $self->{dataIn} ne "" ) {
173 my $chr = substr($self->{dataIn}, 0, 1);
175 $self->{inflateStart} = 0;
176 if ( $chr eq chr(0xd6) || $chr eq chr(0xd7) ) {
178 # Flag 0xd6 or 0xd7 means this is a compressed file with
179 # appended md4 block checksums for rsync. Change
180 # the first byte back to 0x78 and proceed.
182 ##print("Got 0xd6/0xd7 block: normal\n");
183 substr($self->{dataIn}, 0, 1) = chr(0x78);
184 } elsif ( $chr eq chr(0xb3) ) {
186 # Flag 0xb3 means this is the start of the rsync
187 # block checksums, so consider this as EOF for
188 # the compressed file. Also seek the file so
189 # it is positioned at the 0xb3.
191 sysseek($self->{fh}, -length($self->{dataIn}), 1);
193 $self->{dataIn} = "";
194 ##print("Got 0xb3 block: considering eof\n");
198 # normal case: nothing to do
202 my($data, $err) = $self->{inflate}->inflate($self->{dataIn});
203 $self->{dataOut} .= $data;
204 if ( $err == Z_STREAM_END ) {
206 $self->{inflate} = $self->myInflateInit;
207 $self->{inflateStart} = 1;
208 } elsif ( $err != Z_OK ) {
213 if ( $nRead >= length($self->{dataOut}) ) {
214 $n = length($self->{dataOut});
215 $$dataRef = $self->{dataOut};
216 $self->{dataOut} = '';
219 $$dataRef = substr($self->{dataOut}, 0, $nRead);
220 $self->{dataOut} = substr($self->{dataOut}, $nRead);
226 # Provide a line-at-a-time interface. This splits and buffers the
227 # lines, you cannot mix calls to read() and readLine().
234 $self->{readLineBuf} = [] if ( !defined($self->{readLineBuf}) );
235 while ( !@{$self->{readLineBuf}} ) {
236 $self->read(\$str, $CompMaxRead);
238 $str = $self->{readLineFrag};
239 $self->{readLineFrag} = "";
240 $str = decode_utf8($str) if ( $self->{utf8} );
243 @{$self->{readLineBuf}} = split(/\n/, $self->{readLineFrag} . $str);
244 if ( substr($str, -1, 1) ne "\n" ) {
245 $self->{readLineFrag} = pop(@{$self->{readLineBuf}});
247 $self->{readLineFrag} = "";
250 $str = shift(@{$self->{readLineBuf}}) . "\n";
251 if ( $self->{utf8} ) {
252 my $strUtf8 = decode_utf8($str, 0);
253 $strUtf8 = $str if ( length($strUtf8) == 0 );
263 return if ( $self->{write} );
264 return sysseek($self->{fh}, 0, 0) if ( !$self->{compress} );
265 $self->{dataOut} = '';
266 $self->{dataIn} = '';
268 $self->{inflate} = $self->myInflateInit;
269 $self->{inflateStart} = 1;
270 return sysseek($self->{fh}, 0, 0);
276 my($data, $force) = @_;
279 # Buffer small writes using a buffer size of up to $WriteBufSize.
281 if ( $force || length($self->{writeBuf}) + length($data) > $WriteBufSize ) {
282 if ( length($self->{writeBuf}) ) {
283 my $wrData = $self->{writeBuf} . $data;
284 return -1 if ( syswrite($self->{fh}, $wrData) != length($wrData) );
285 $self->{writeBuf} = undef;
287 return if ( length($data) == 0 );
288 return -1 if ( syswrite($self->{fh}, $data) != length($data) );
291 $self->{writeBuf} .= $data;
298 my($self, $dataRef) = @_;
299 my $n = length($$dataRef);
301 return if ( !$self->{write} );
302 print(STDERR $$dataRef) if ( $self->{writeTeeStderr} );
303 return 0 if ( $n == 0 );
304 if ( !$self->{compress} ) {
306 # If smbclient gets a read error on the client (due to a file lock)
307 # it will write a dummy file of zeros. We detect this so we can
308 # store the file efficiently as a sparse file. writeZeroCnt is
309 # the number of consecutive 0 bytes at the start of the file.
312 if ( $self->{writeZeroCnt} >= 0 && $$dataRef =~ /^(\0+)/s ) {
314 $self->{writeZeroCnt} += $skip;
315 return $n if ( $skip == $n );
318 # We now have some non-zero bytes, so time to seek to the right
319 # place and turn off zero-byte detection.
321 if ( $self->{writeZeroCnt} > 0 ) {
322 sysseek($self->{fh}, $self->{writeZeroCnt}, 0);
323 $self->{writeZeroCnt} = -1;
324 } elsif ( $self->{writeZeroCnt} == 0 ) {
325 $self->{writeZeroCnt} = -1;
327 return -1 if ( $self->writeBuffered(substr($$dataRef, $skip)) < 0 );
330 for ( my $i = 0 ; $i < $n ; $i += $CompMaxWrite ) {
331 my $dataIn = substr($$dataRef, $i, $CompMaxWrite);
332 my $dataOut = $self->{deflate}->deflate($dataIn);
333 return -1 if ( $self->writeBuffered($dataOut) < 0 );
334 $self->{deflateIn} += length($dataIn);
335 $self->{deflateOut} += length($dataOut);
336 if ( $self->{deflateIn} >= $CompMaxWrite ) {
337 if ( $self->{deflateOut} < $CompMaxRead ) {
339 # Compression is too high: to avoid huge memory requirements
340 # on read we need to flush().
342 $dataOut = $self->{deflate}->flush();
344 $self->{deflate} = $self->myDeflateInit;
345 return -1 if ( $self->writeBuffered($dataOut) < 0 );
347 $self->{deflateIn} = $self->{deflateOut} = 0;
357 return $self->{name};
362 my($self, $param) = @_;
364 $self->{writeTeeStderr} = $param if ( defined($param) );
365 return $self->{writeTeeStderr};
373 if ( $self->{write} && $self->{compress} ) {
374 my $data = $self->{deflate}->flush();
375 $err = 1 if ( $self->writeBuffered($data) < 0 );
376 } elsif ( $self->{write} && !$self->{compress} ) {
377 if ( $self->{writeZeroCnt} > 0 ) {
379 # We got a file of all zero bytes. Write a single zero byte
380 # at the end of the file. On most file systems this is an
381 # efficient way to store the file.
383 $err = 1 if ( sysseek($self->{fh}, $self->{writeZeroCnt} - 1, 0)
384 != $self->{writeZeroCnt} - 1
385 || syswrite($self->{fh}, "\0") != 1 );
388 $self->writeBuffered(undef, 1);
390 return $err ? -1 : 0;
394 # If $compress is >0, copy and compress $srcFile putting the output
395 # in $destFileZ. Otherwise, copy the file to $destFileNoZ, or do
396 # nothing if $destFileNoZ is undef. Finally, if rename is set, then
397 # the source file is removed.
401 my($class, $srcFile, $destFileZ, $destFileNoZ, $compress, $rmSrc) = @_;
402 my(@s) = stat($srcFile);
403 my $atime = $s[8] =~ /(.*)/ && $1;
404 my $mtime = $s[9] =~ /(.*)/ && $1;
405 if ( $CompZlibOK && $compress > 0 ) {
406 my $fh = BackupPC::FileZIO->open($destFileZ, 1, $compress);
408 if ( defined($fh) && open(LOG, "<", $srcFile) ) {
410 while ( sysread(LOG, $data, 65536) > 0 ) {
415 unlink($srcFile) if ( $rmSrc );
416 utime($atime, $mtime, $destFileZ);
419 $fh->close() if ( defined($fh) );
423 return 0 if ( !defined($destFileNoZ) );
425 return rename($srcFile, $destFileNoZ);
427 return 0 if ( !copy($srcFile, $destFileNoZ) );
428 utime($atime, $mtime, $destFileNoZ);