3388 lines
		
	
	
		
			115 KiB
		
	
	
	
		
			Perl
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			3388 lines
		
	
	
		
			115 KiB
		
	
	
	
		
			Perl
		
	
	
		
			Executable File
		
	
	
	
	
| #! /usr/bin/perl -w
 | |
| # arclog: Archive the log files monthly
 | |
| 
 | |
| # Copyright (c) 2001-2022 imacat.
 | |
| #
 | |
| # Licensed under the Apache License, Version 2.0 (the "License");
 | |
| # you may not use this file except in compliance with the License.
 | |
| # You may obtain a copy of the License at
 | |
| #
 | |
| #     http://www.apache.org/licenses/LICENSE-2.0
 | |
| #
 | |
| # Unless required by applicable law or agreed to in writing, software
 | |
| # distributed under the License is distributed on an "AS IS" BASIS,
 | |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
| # See the License for the specific language governing permissions and
 | |
| # limitations under the License.
 | |
| 
 | |
| # First written: 2001/1/5
 | |
| 
 | |
| package main;
 | |
| use 5.008;
 | |
| use strict;
 | |
| use warnings;
 | |
| use ExtUtils::MakeMaker qw();
 | |
| use Fcntl qw(:flock);
 | |
| use File::Basename qw(basename);
 | |
| use IO::Handle qw(autoflush);
 | |
| use Getopt::Long qw(GetOptions);
 | |
| use Cwd qw(cwd);
 | |
| use File::Basename qw(basename fileparse);
 | |
| use File::Spec::Functions qw(devnull file_name_is_absolute path catfile
 | |
|     splitdir curdir updir);
 | |
| use File::Temp qw(tempfile);
 | |
| use Config qw();
 | |
| use base qw(Exporter);
 | |
| our (@EXPORT, @EXPORT_OK);
 | |
| BEGIN {
 | |
| @EXPORT = qw(
 | |
| COMPRESS_GZIP COMPRESS_BZIP2 COMPRESS_XZ COMPRESS_NONE
 | |
| OVERRIDE_OVERWRITE OVERRIDE_APPEND OVERRIDE_IGNORE OVERRIDE_FAIL OVERRIDE_ASK
 | |
| KEEP_ALL KEEP_RESTART KEEP_DELETE KEEP_THIS_MONTH
 | |
| TYPE_PLAIN TYPE_GZIP TYPE_BZIP2 TYPE_XZ
 | |
| TMP_SUFFIX where_is to_yyyymm format_number rel2abs);
 | |
| @EXPORT_OK = @EXPORT;
 | |
| # Prototype declaration
 | |
| sub main();
 | |
| sub parse_args();
 | |
| sub where_is($);
 | |
| sub to_yyyymm($);
 | |
| sub format_number($);
 | |
| sub rel2abs($;$);
 | |
| }
 | |
| 
 | |
| our ($THIS_FILE, $VERBOSE);
 | |
| our ($VERSION);
 | |
| $THIS_FILE = basename($0);
 | |
| $VERSION = "3.05";
 | |
| $VERBOSE = 1;
 | |
| 
 | |
| our (%CONF, @LOGFILES, $THIS_MONTH, $PROGRESS_BAR);
 | |
| our (%WHERE_IS);
 | |
| $THIS_MONTH = to_yyyymm $^T;
 | |
| 
 | |
| # Constants
 | |
| # The compress mode
 | |
| use constant COMPRESS_GZIP => "gzip";
 | |
| use constant COMPRESS_BZIP2 => "bzip2";
 | |
| use constant COMPRESS_XZ => "xz";
 | |
| use constant COMPRESS_NONE => "none";
 | |
| use constant DEFAULT_COMPRESS => COMPRESS_GZIP;
 | |
| # The override mode
 | |
| use constant OVERRIDE_OVERWRITE => "overwrite";
 | |
| use constant OVERRIDE_APPEND => "append";
 | |
| use constant OVERRIDE_IGNORE => "ignore";
 | |
| use constant OVERRIDE_FAIL => "fail";
 | |
| use constant OVERRIDE_ASK => "ask";
 | |
| sub DEFAULT_OVERRIDE() { -t STDIN? OVERRIDE_ASK: OVERRIDE_FAIL; }
 | |
| # The keep mode
 | |
| use constant KEEP_ALL => "all";
 | |
| use constant KEEP_RESTART => "restart";
 | |
| use constant KEEP_DELETE => "delete";
 | |
| use constant KEEP_THIS_MONTH => "this-month";
 | |
| use constant DEFAULT_KEEP => KEEP_THIS_MONTH;
 | |
| # The file types
 | |
| use constant TYPE_PLAIN => "text/plain";
 | |
| use constant TYPE_GZIP => "application/x-gzip";
 | |
| use constant TYPE_BZIP2 => "application/x-bzip2";
 | |
| use constant TYPE_XZ => "application/x-xz";
 | |
| # Other constants
 | |
| use constant TMP_SUFFIX => ".tmp-arclog";
 | |
| use constant GZIP_SUFFIX => ".gz";
 | |
| use constant BZIP2_SUFFIX => ".bz2";
 | |
| use constant DEFAULT_IS_PROGRESS_BAR => 1;
 | |
| use constant DEFAULT_SORT => 0;
 | |
| 
 | |
| our ($VER_MSG, $HELP_MSG, $SHORT_HELP);
 | |
| $VER_MSG = "$THIS_FILE v$VERSION by imacat <imacat\@mail.imacat.idv.tw>";
 | |
| $SHORT_HELP = "Try `$THIS_FILE --help' for more information.";
 | |
| $HELP_MSG = << "EOT";
 | |
| Usage: $THIS_FILE [options] logfile... [output]
 | |
| Archive the log files monthly.
 | |
| 
 | |
|   logfile            The log file to be archived.
 | |
|   output             The prefix of the output files.  The output files are
 | |
|                      named as pre.yyyymm, ie: pre.200001, pre.200002.  If not
 | |
|                      specified, the default prefix is the logfile pathname.
 | |
|   --compress method  Compress the archived files.   Available methods are:
 | |
|                      gzip, bzip2, xz, and none.  The default is gzip.
 | |
|   --sort             Sort the records in the log files by time.
 | |
|   --nosort           Do not sort the records. (default)
 | |
|   --override mode    The override behavior when the target archived files
 | |
|                      exist.  Available modes are: overwrite, append, ignore,
 | |
|                      fail and ask.  If not specified, the default is "ask" on
 | |
|                      TTY, "fail" for else.
 | |
|   --keep mode        What to keep in the logfile.  Available modes are: all,
 | |
|                      restart, delete and this-month.  If not specified, the
 | |
|                      default is "this-month".
 | |
|   -d,--debug         Display debug messages.  Multiple --debug to debug more.
 | |
|   -q,--quiet         Disable debug messages.  An opposite that cancels the
 | |
|                      effect of --debug.
 | |
|   -h,--help          Display this help.
 | |
|   -v,--version       Display version number.
 | |
| 
 | |
| EOT
 | |
| 
 | |
| main;
 | |
| exit 0;
 | |
| 
 | |
| # Main program
 | |
| sub main() {
 | |
|     local ($_, %_);
 | |
|     my %ARC;
 | |
| 
 | |
|     # Parse the arguments
 | |
|     parse_args;
 | |
| 
 | |
|     # Create the temporary working files
 | |
|     $_->create_temp foreach @LOGFILES;
 | |
|     # Read the source files to temporary working files
 | |
|     $_->read_source foreach @LOGFILES;
 | |
|     # Process each log file
 | |
|     %ARC = qw();
 | |
|     foreach my $logfile (@LOGFILES) {
 | |
|         my ($label, $count, $dropped);
 | |
|         print STDERR "Archiving " . $logfile->{"file"} . " ... "
 | |
|             if $VERBOSE > 0 && !$CONF{"IS_PROGRESS_BAR"};
 | |
|         print STDERR "\n" if $VERBOSE > 1 && !$CONF{"IS_PROGRESS_BAR"};
 | |
|         $label = $logfile->{"file"};
 | |
|         $label = "-" . substr($label, -13) if length $label > 14;
 | |
|         ($count, $dropped) = (0, 0);
 | |
|         if ($CONF{"IS_PROGRESS_BAR"}) {
 | |
|             $PROGRESS_BAR = _private::ProgressBar->new($label, $logfile->{"count"});
 | |
|             $PROGRESS_BAR->update(0);
 | |
|         }
 | |
|         # Sort each log record by month
 | |
|         while (defined($_ = $logfile->read_record)) {
 | |
|             my $month;
 | |
|             $month = $logfile->{"format"}->parse_month($_);
 | |
|             # Skip malformed records whose time is not parsable
 | |
|             if (!defined $month) {
 | |
|                 $dropped++;
 | |
| 
 | |
|             # This month to keep
 | |
|             } elsif ($CONF{"KEEP"} eq KEEP_THIS_MONTH && $month eq $THIS_MONTH) {
 | |
|                 $logfile->save_this_month($_);
 | |
| 
 | |
|             # Months to archive
 | |
|             } else {
 | |
|                 # A new month
 | |
|                 $ARC{$month} = _private::Archive->new($month)
 | |
|                     if !exists $ARC{$month};
 | |
|                 $ARC{$month}->add($_) if !$ARC{$month}->{"ignore"};
 | |
|             }
 | |
|             $count++;
 | |
|             $PROGRESS_BAR->update($count) if $CONF{"IS_PROGRESS_BAR"};
 | |
|         }
 | |
|         undef $PROGRESS_BAR if $CONF{"IS_PROGRESS_BAR"};
 | |
|         print STDERR "$count records\n"
 | |
|             if $VERBOSE > 0 && !$CONF{"IS_PROGRESS_BAR"};
 | |
|         warn "Dropping $dropped malformed records\n"
 | |
|             if $dropped > 0;
 | |
|     }
 | |
|     # Sorting
 | |
|     if ($CONF{"SORT"}) {
 | |
|         foreach my $month (sort grep !$ARC{$_}->{"ignore"}, keys %ARC) {
 | |
|             $ARC{$month}->sort;
 | |
|         }
 | |
|     }
 | |
|     # Store the archived log records
 | |
|     foreach my $month (sort grep !$ARC{$_}->{"ignore"}, keys %ARC) {
 | |
|         $ARC{$month}->store_archive;
 | |
|     }
 | |
|     # Return the records of this month
 | |
|     if ($CONF{"KEEP"} eq KEEP_THIS_MONTH) {
 | |
|         $_->restore_this_month foreach @LOGFILES;
 | |
|     }
 | |
|     # Remove the temporarily working files
 | |
|     $_->remove_temp foreach @LOGFILES;
 | |
| 
 | |
|     # Print the statistics
 | |
|     printf STDERR "%d archive files written, %d seconds elapsed.\n",
 | |
|             scalar(grep !$ARC{$_}->{"ignore"}, keys %ARC), (time - $^T)
 | |
|         if $VERBOSE > 0;
 | |
|     return;
 | |
| }
 | |
| 
 | |
| # Parse the arguments
 | |
| sub parse_args() {
 | |
|     local ($_, %_);
 | |
|     my ($has_stdin, $one_arg);
 | |
| 
 | |
|     %CONF = qw();
 | |
|     $CONF{"SORT"} = DEFAULT_SORT;
 | |
|     # Get the arguments
 | |
|     eval {
 | |
|         local $SIG{"__WARN__"} = sub { die $_[0]; };
 | |
|         Getopt::Long::Configure(qw(no_auto_abbrev bundling));
 | |
|         GetOptions( "compress|c=s"=>sub {
 | |
|                         if ($_[1] =~ /^(?:g|gzip)$/i) {
 | |
|                             $CONF{"COMPRESS"} = COMPRESS_GZIP;
 | |
|                         } elsif ($_[1] =~ /^(?:b|bzip2)$/i) {
 | |
|                             $CONF{"COMPRESS"} = COMPRESS_BZIP2;
 | |
|                         } elsif ($_[1] =~ /^(?:x|xz)$/i) {
 | |
|                             $CONF{"COMPRESS"} = COMPRESS_XZ;
 | |
|                         } elsif ($_[1] =~ /^(?:n|none)$/i) {
 | |
|                             $CONF{"COMPRESS"} = COMPRESS_NONE;
 | |
|                         } else {
 | |
|                             die "$THIS_FILE: Unknown compress mode: $_[1]\n";
 | |
|                         } },
 | |
|                     "nocompress"=>sub { $CONF{"COMPRESS"} = COMPRESS_NONE; },
 | |
|                     "sort|s!"=>\$CONF{"SORT"},
 | |
|                     "override|o=s"=>sub {
 | |
|                         if ($_[1] =~ /^(?:o|overwrite)$/i) {
 | |
|                             $CONF{"OVERRIDE"} = OVERRIDE_OVERWRITE;
 | |
|                         } elsif ($_[1] =~ /^(?:a|append)$/i) {
 | |
|                             $CONF{"OVERRIDE"} = OVERRIDE_APPEND;
 | |
|                         } elsif ($_[1] =~ /^(?:i|ignore)$/i) {
 | |
|                             $CONF{"OVERRIDE"} = OVERRIDE_IGNORE;
 | |
|                         } elsif ($_[1] =~ /^(?:f|fail)$/i) {
 | |
|                             $CONF{"OVERRIDE"} = OVERRIDE_FAIL;
 | |
|                         } elsif ($_[1] =~ /^(?:ask)$/i) {
 | |
|                             $CONF{"OVERRIDE"} = OVERRIDE_ASK;
 | |
|                         } else {
 | |
|                             die "$THIS_FILE: Unknown override mode: $_[1]\n";
 | |
|                         } },
 | |
|                     "keep|k=s"=>sub {
 | |
|                         if ($_[1] =~ /^(?:a|all)$/i) {
 | |
|                             $CONF{"KEEP"} = KEEP_ALL;
 | |
|                         } elsif ($_[1] =~ /^(?:r|restart)$/i) {
 | |
|                             $CONF{"KEEP"} = KEEP_RESTART;
 | |
|                         } elsif ($_[1] =~ /^(?:d|delete)$/i) {
 | |
|                             $CONF{"KEEP"} = KEEP_DELETE;
 | |
|                         } elsif ($_[1] =~ /^(?:t|this-month)$/i) {
 | |
|                             $CONF{"KEEP"} = KEEP_THIS_MONTH;
 | |
|                         } else {
 | |
|                             die "$THIS_FILE: Unknown keep mode: $_[1]\n";
 | |
|                         } },
 | |
|                     "debug|d+"=>\$VERBOSE,
 | |
|                     "quiet|q"=>sub { $VERBOSE-- if $VERBOSE > 0; },
 | |
|                     "help|h"=>sub { print $HELP_MSG; exit 0; },
 | |
|                     "version|v"=>sub { print "$VER_MSG\n"; exit 0; });
 | |
|     };
 | |
|     die "$THIS_FILE: $@$SHORT_HELP\n" if $@ ne "";
 | |
| 
 | |
|     # Save the original STDIN and STDOUT
 | |
|     open $STDIN, "<&", \*STDIN          or die "$THIS_FILE: STDIN: $!";
 | |
|     open $STDOUT, ">&", \*STDOUT        or die "$THIS_FILE: STDOUT: $!";
 | |
| 
 | |
|     # Set the verbose level
 | |
|     autoflush STDERR if $VERBOSE > 1;
 | |
|     $CONF{"IS_PROGRESS_BAR"} = DEFAULT_IS_PROGRESS_BAR;
 | |
|     $CONF{"IS_PROGRESS_BAR"} = 0 if $VERBOSE == 0 || !-t STDERR;
 | |
|     if ($CONF{"IS_PROGRESS_BAR"}) {
 | |
|         # Check if we have Term::ReadKey
 | |
|         $CONF{"IS_PROGRESS_BAR"} = 0 unless eval { require Term::ReadKey; 1; };
 | |
|     }
 | |
| 
 | |
|     # Check the arguments
 | |
|     # Arguments are source files
 | |
|     @LOGFILES = qw();
 | |
|     while (@ARGV > 0) {
 | |
|         $_ = shift @ARGV;
 | |
|         # Treat /dev/stdin as - on UNIX-like systems
 | |
|         $_ = "-" if $_ eq "/dev/stdin" && devnull eq "/dev/null";
 | |
|         push @LOGFILES, $_;
 | |
|         $_{$_} = 1;
 | |
|     }
 | |
|     die "$THIS_FILE: Which log file do you want to archive?\n$SHORT_HELP\n"
 | |
|         if @LOGFILES == 0;
 | |
|     $has_stdin = scalar grep $_ eq "-", @LOGFILES;
 | |
|     # The output prefix
 | |
|     $one_arg = (@LOGFILES == 1);
 | |
|     if ($one_arg) {
 | |
|         # STDIN must specify the output prefix
 | |
|         die "$THIS_FILE: You must specify the output prefix for STDIN\n$SHORT_HELP\n"
 | |
|             if $LOGFILES[0] eq "-";
 | |
|         $CONF{"OUTPUT"} = $LOGFILES[0];
 | |
|     } else {
 | |
|         $CONF{"OUTPUT"} = pop @LOGFILES;
 | |
|         die "$THIS_FILE: You cannot specify STDOUT as the output prefix\n$SHORT_HELP\n"
 | |
|             if $CONF{"OUTPUT"} eq "-";
 | |
|     }
 | |
|     # Check the duplicates - after removing the output prefix
 | |
|     %_ = qw();
 | |
|     foreach (@LOGFILES) {
 | |
|         die "$THIS_FILE: $_: You can only specify a file once\n$SHORT_HELP\n"
 | |
|             if exists $_{$_};
 | |
|         $_{$_} = 1;
 | |
|     }
 | |
| 
 | |
|     # Set the default override mode
 | |
|     $CONF{"OVERRIDE"} = DEFAULT_OVERRIDE if !exists $CONF{"OVERRIDE"};
 | |
|     # Set the default keep mode
 | |
|     $CONF{"KEEP"} = DEFAULT_KEEP if !exists $CONF{"KEEP"};
 | |
|     # Set the default compress mode
 | |
|     $CONF{"COMPRESS"} = DEFAULT_COMPRESS if !exists $CONF{"COMPRESS"};
 | |
| 
 | |
|     # Cannot keep the records of this month back in STDIN
 | |
|     if ($has_stdin && $CONF{"KEEP"} eq KEEP_THIS_MONTH) {
 | |
|         warn "$THIS_FILE: Cannot keep this-month in STDIN.  Change to keep all.\n";
 | |
|         $CONF{"KEEP"} = KEEP_ALL;
 | |
|     }
 | |
|     # Cannot delete STDIN
 | |
|     if ($has_stdin && $CONF{"KEEP"} eq KEEP_DELETE) {
 | |
|         warn "$THIS_FILE: Cannot delete the STDIN.  Change to keep all.\n";
 | |
|         $CONF{"KEEP"} = KEEP_ALL;
 | |
|     }
 | |
|     # Cannot restart STDIN
 | |
|     if ($has_stdin && $CONF{"KEEP"} eq KEEP_RESTART) {
 | |
|         warn "$THIS_FILE: Cannot restart the STDIN.  Change to keep all.\n";
 | |
|         $CONF{"KEEP"} = KEEP_ALL;
 | |
|     }
 | |
|     # Cannot get the log file and the answer both from STDIN
 | |
|     if ($has_stdin && $CONF{"OVERRIDE"} eq OVERRIDE_ASK) {
 | |
|         warn "$THIS_FILE: Cannot read from STDIN in ask mode.  Change to fail mode.\n";
 | |
|         $CONF{"OVERRIDE"} = "fail";
 | |
|     }
 | |
| 
 | |
|     # Check the log files
 | |
|     @LOGFILES = map _private::LogFile->new($_), @LOGFILES;
 | |
|     if ((@_ = grep $_->{"is_empty"}, @LOGFILES) > 0) {
 | |
|         print STDERR "Skipping empty files: " . join(", ", map $_->{"file"}, @_) . "\n"
 | |
|             if $VERBOSE > 0;
 | |
|         @LOGFILES = grep !$_->{"is_empty"}, @LOGFILES;
 | |
|         # Close empty files - do this after $_->{"is_empty"},
 | |
|         #   so that $_->{"is_empty"} is still accessible.
 | |
|         foreach (@_) {
 | |
|             $_->{"io"}->close;
 | |
|             undef $_;
 | |
|         }
 | |
|         if (@LOGFILES == 0) {
 | |
|             print STDERR "$THIS_FILE: No non-empty files left.  Exiting.\n"
 | |
|                 if $VERBOSE > 0;
 | |
|             exit 0;
 | |
|         }
 | |
|         $has_stdin = scalar grep $_->{"stdin"}, @LOGFILES;
 | |
|     }
 | |
|     # Check if the formats of the files are consistent
 | |
|     %_ = map { $_->{"format"} => 1 } @LOGFILES;
 | |
|     die "$THIS_FILE: Cannot archive log files in different formats at a time.\n"
 | |
|             . join "", map sprintf("  %s : %s\n", $_->{"file"}, $_->{"format"}),
 | |
|                 @LOGFILES
 | |
|         if keys %_ > 1;
 | |
|     $CONF{"FORMAT"} = $LOGFILES[0]->{"format"};
 | |
| 
 | |
|     # Check the output file prefix
 | |
|     # Strip the filename suffix of the compressed files
 | |
|     if ($one_arg) {
 | |
|         $CONF{"OUTPUT"} =~ s/\.gz$// if $LOGFILES[0]->{"type"} eq TYPE_GZIP;
 | |
|         $CONF{"OUTPUT"} =~ s/\.bz2$// if $LOGFILES[0]->{"type"} eq TYPE_BZIP2;
 | |
|         $CONF{"OUTPUT"} =~ s/\.xz// if $LOGFILES[0]->{"type"} eq TYPE_XZ;
 | |
|     }
 | |
|     die "$THIS_FILE: Please specify output prefix\n$SHORT_HELP\n"
 | |
|         if !defined $CONF{"OUTPUT"};
 | |
|     $CONF{"OUTPUT"} = rel2abs $CONF{"OUTPUT"};
 | |
|     $_ = (fileparse $CONF{"OUTPUT"})[1];
 | |
|     die "$THIS_FILE: $_: Not found\n$SHORT_HELP\n"
 | |
|         if !-e $_;
 | |
|     die "$THIS_FILE: $_: Not a directory\n$SHORT_HELP\n"
 | |
|         if !-d $_;
 | |
|     die "$THIS_FILE: $_: Permission denied\n$SHORT_HELP\n"
 | |
|         if !-w $_;
 | |
| 
 | |
|     return;
 | |
| }
 | |
| 
 | |
| # Find an executable
 | |
| #   Code inspired from CPAN::FirstTime
 | |
| sub where_is($) {
 | |
|     local ($_, %_);
 | |
|     my ($file, $path);
 | |
|     $file = $_[0];
 | |
|     return $WHERE_IS{$file} if exists $WHERE_IS{$file};
 | |
|     foreach my $dir (path) {
 | |
|         print STDERR "    Checking $dir ... " if $VERBOSE > 3;
 | |
|         if (defined($path = MM->maybe_command(catfile($dir, $file)))) {
 | |
|             print STDERR "$path\n  found " if $VERBOSE > 3;
 | |
|             return ($WHERE_IS{$file} = $path);
 | |
|         }
 | |
|         print STDERR "no\n" if $VERBOSE > 3;
 | |
|     }
 | |
|     return ($WHERE_IS{$file} = undef);
 | |
| }
 | |
| 
 | |
| # convert timestamp to yyyymm
 | |
| sub to_yyyymm($) {
 | |
|     local ($_, %_);
 | |
|     @_ = localtime $_[0];
 | |
|     return sprintf "%04d%02d", $_[5] + 1900, $_[4] + 1;
 | |
| }
 | |
| 
 | |
| # Format the number every 3 digit
 | |
| sub format_number($) {
 | |
|     local $_;
 | |
|     $_ = $_[0];
 | |
|     # Group every 3 digit
 | |
|     $_ = $1 . "," . $2 . $3 while /^([^\.]*\d)(\d\d\d)(.*)$/;
 | |
|     return $_;
 | |
| }
 | |
| 
 | |
| # Convert a relative path to an absolute path
 | |
| sub rel2abs($;$) {
 | |
|     local ($_, %_);
 | |
|     my ($path, $base);
 | |
|     ($path, $base) = @_;
 | |
| 
 | |
|     # Turn the base absolute
 | |
|     $base = cwd unless defined $base;
 | |
|     $base = rel2abs $base if !file_name_is_absolute $base;
 | |
| 
 | |
|     # Deal with the ~ user home directories under UNIX
 | |
|     if (defined $Config::Config{"d_getpwent"}) {
 | |
|         @_ = splitdir($path);
 | |
|         # If it starts from the user home directory
 | |
|         if ($_[0] =~ /^~(.*)$/) {
 | |
|             my ($user, @pwent, $home);
 | |
|             $user = $1;
 | |
|             # The same as the current user
 | |
|             if (    (@pwent = getpwuid $>) > 0
 | |
|                     && ($user eq "" || $user eq $pwent[0])) {
 | |
|                 # Replace with the user home directory
 | |
|                 # Respect the HOME environment variable if exists
 | |
|                 $home = exists $ENV{"HOME"}? $ENV{"HOME"}: $pwent[7];
 | |
|                 @_ = (splitdir($home), @_[1...$#_]);
 | |
|             # Get the user home directory
 | |
|             } elsif ((@pwent = getpwnam $user) > 0) {
 | |
|                 # Replace with the user home directory
 | |
|                 $home = $pwent[7];
 | |
|                 @_ = (splitdir($home), @_[1...$#_]);
 | |
|             }
 | |
|             # Compose the path
 | |
|             $path = catfile @_;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     # Append the current directory if relative
 | |
|     $path = catfile($base, $path) unless file_name_is_absolute $path;
 | |
| 
 | |
|     @_ = splitdir($path);           # Split into directory components
 | |
|     # Add an empty filename level if last level is a directory
 | |
|     push @_, "" if ($_[@_-1] eq curdir || $_[@_-1] eq updir);
 | |
|     for ($_ = 1; $_ < @_; $_++) {   # Parse each level one by one
 | |
|         # If it is this directory
 | |
|         if ($_[$_] eq curdir) {
 | |
|             splice @_, $_, 1;       # Remove this level directly
 | |
|             $_--;                   # The level number drop by 1
 | |
|         # If it is the parent directory
 | |
|         } elsif ($_ > 1 && $_[$_] eq updir && $_[$_-1] ne updir) {
 | |
|             splice @_, $_-1, 2;     # Remove this and the previous level
 | |
|             $_ -= 2;                # The level number drop by 2
 | |
|         }
 | |
|     }
 | |
|     $path = catfile @_;             # Compose the full path
 | |
|     return $path;
 | |
| }
 | |
| 
 | |
| 
 | |
| # _private::ProgressBar: The progress bar display
 | |
| package _private::ProgressBar;
 | |
| use 5.008;
 | |
| use strict;
 | |
| use warnings;
 | |
| 
 | |
| # Initialize the progress bar handler
 | |
| sub new : method {
 | |
|     local ($_, %_);
 | |
|     my ($class, $self, $label, $total);
 | |
|     ($class, $label, $total) = @_;
 | |
|     $self = bless {}, $class;
 | |
|     $self->{"label"} = $label;
 | |
|     $self->{"total"} = $total;
 | |
|     $self->{"start"} = time;
 | |
|     $self->{"last_line"} = undef;
 | |
|     require Term::ReadKey;
 | |
|     return $self;
 | |
| }
 | |
| 
 | |
| # Update the progress bar
 | |
| sub update : method {
 | |
|     local ($_, %_);
 | |
|     my ($self, $cur, $label, $width, $elapsed, $min, $sec, $bar, $line);
 | |
|     ($self, $cur, $label) = @_;
 | |
|     $label = $self->{"label"} if !defined $label;
 | |
| 
 | |
|     # Not enough space for a progress bar
 | |
|     return if ($width = (Term::ReadKey::GetTerminalSize())[0] - 30) < 1;
 | |
|     # Calculate the elapsed time
 | |
|     $elapsed = time - $self->{"start"};
 | |
|     $sec = $elapsed % 60;
 | |
|     $min = ($elapsed - $sec) / 60;
 | |
|     # Calculate the percentage and the progress bar
 | |
|     $bar = "=" x sprintf("%1.0f", ($cur / $self->{"total"}) * $width);
 | |
|     # Compose the line
 | |
|     $line = sprintf "\r%-14.14s [%-".$width."s] %3.0f%% %02d:%02d",
 | |
|         $label, $bar, ($cur / $self->{"total"}) * 100, $min, $sec;
 | |
|     # Print if changed
 | |
|     if (!defined $self->{"last_line"} || $self->{"last_line"} ne $line) {
 | |
|         # Print it
 | |
|         print STDERR "\r$line";
 | |
|         # Record the current line
 | |
|         $self->{"last_line"} = $line;
 | |
|     }
 | |
|     # Finished
 | |
|     print STDERR "\n" if $cur == $self->{"total"};
 | |
|     return;
 | |
| }
 | |
| 
 | |
| 
 | |
| # _private::LogFile: The source log file
 | |
| package _private::LogFile;
 | |
| use 5.008;
 | |
| use strict;
 | |
| use warnings;
 | |
| BEGIN {
 | |
| import main;
 | |
| }
 | |
| 
 | |
| use Fcntl qw(:flock :seek);
 | |
| use File::Basename qw(fileparse);
 | |
| use File::Temp qw(tempfile);
 | |
| 
 | |
| # Constants
 | |
| # The file type checkers
 | |
| use constant MAGIC_PM => "File::MMagic";
 | |
| use constant MAGIC_EXEC => "file";
 | |
| use constant MAGIC_SUFFIX => "suffix";
 | |
| 
 | |
| our ($MAGIC_METHOD, $MAGIC);
 | |
| BEGIN {
 | |
| undef $MAGIC_METHOD;
 | |
| undef $MAGIC;
 | |
| }
 | |
| 
 | |
| # Initialize the source log file processor
 | |
| sub new : method {
 | |
|     local ($_, %_);
 | |
|     my ($class, $self, $file, $FH, $f0);
 | |
|     ($class, $file) = @_;
 | |
| 
 | |
|     # STDIN is another class
 | |
|     if ($file eq "-") {
 | |
|         $class .= "::STDIN";
 | |
|         return $class->new(@_[1...$#_]);
 | |
|     }
 | |
| 
 | |
|     $self = bless {}, $class;
 | |
|     $self->{"stdin"} = 0;
 | |
|     $self->{"keep"} = $CONF{"KEEP"};
 | |
|     $self->{"override"} = $CONF{"OVERRIDE"};
 | |
|     $self->{"tmp"} = undef;
 | |
| 
 | |
|     # Load the File::MMagic first before opening anything, or the seek
 | |
|     #   method will not be loaded into IO::Handle
 | |
|     $self->check_magic;
 | |
|     $self->{"check_type"} = $file if $MAGIC_METHOD eq MAGIC_EXEC;
 | |
| 
 | |
|     $self->{"file"} = rel2abs $file;
 | |
|     ($f0, $file) = ($file, $self->{"file"});
 | |
|     # Open the file
 | |
|     if ($self->{"keep"} eq KEEP_ALL) {
 | |
|         open $FH, $file                 or die "$THIS_FILE: $file: $!";
 | |
|         flock $FH, LOCK_SH;
 | |
|     } else {
 | |
|         open $FH, "+<", $file           or die "$THIS_FILE: $file: $!";
 | |
|         flock $FH, LOCK_EX;
 | |
|     }
 | |
|     $self->{"FH"} = $FH;
 | |
| 
 | |
|     # Check the file type
 | |
|     print STDERR "Checking file type of $f0 ... " if $VERBOSE > 1;
 | |
|     $self->{"type"} = $self->check_type;
 | |
|     print STDERR $self->{"type"} . "\n" if $VERBOSE > 1;
 | |
|     # Check the I/O handler to use
 | |
|     $self->{"io"} = $self->check_io;
 | |
|     # Open the file
 | |
|     $self->{"io"}->open_read($file, $self->{"FH"});
 | |
|     # Check the log file format
 | |
|     $self->{"format"} = $self->check_format;
 | |
|     # Not empty
 | |
|     if (!$self->{"is_empty"}) {
 | |
|         # Check the temporarily working file availability
 | |
|         $self->{"temp"} = $self->check_temp;
 | |
|     }
 | |
| 
 | |
|     return $self;
 | |
| }
 | |
| 
 | |
| # Check the temporarily working file availability
 | |
| sub check_temp : method {
 | |
|     local ($_, %_);
 | |
|     my ($self, $file, $dir, $suf);
 | |
|     $self = $_[0];
 | |
| 
 | |
|     # No need to create a named temporarily file if we keep the log file
 | |
|     if ($CONF{"KEEP"} eq KEEP_ALL) {
 | |
|         # Create an anonymous temporary file
 | |
|         return undef;
 | |
|     }
 | |
| 
 | |
|     if ($self->{"type"} eq TYPE_GZIP) {
 | |
|         ($file, $dir, $suf) = fileparse $self->{"file"}, ".gz";
 | |
|     } elsif ($self->{"type"} eq TYPE_BZIP2) {
 | |
|         ($file, $dir, $suf) = fileparse $self->{"file"}, ".bz2";
 | |
|     } elsif ($self->{"type"} eq TYPE_XZ) {
 | |
|         ($file, $dir, $suf) = fileparse $self->{"file"}, ".xz";
 | |
|     } else {
 | |
|         ($file, $dir, $suf) = fileparse $self->{"file"};
 | |
|     }
 | |
| 
 | |
|     $_ = $dir . $file . TMP_SUFFIX;
 | |
|     # Does the temporary working file exists?
 | |
|     die "$THIS_FILE: $_: Temporary working file exists\n$SHORT_HELP\n"
 | |
|         if -e $_;
 | |
| 
 | |
|     # Check if we can create the temporarily working file
 | |
|     die "$THIS_FILE: $dir: File exists\n$SHORT_HELP\n"
 | |
|         if !-e $dir;
 | |
|     die "$THIS_FILE: $dir: Not a directory\n$SHORT_HELP\n"
 | |
|         if !-d $dir;
 | |
|     die "$THIS_FILE: $dir: Permission denied\n$SHORT_HELP\n"
 | |
|         if !-w $dir;
 | |
| 
 | |
|     return $_;
 | |
| }
 | |
| 
 | |
| # Check the log file format
 | |
| sub check_format : method {
 | |
|     local ($_, %_);
 | |
|     my $self;
 | |
|     $self = $_[0];
 | |
| 
 | |
|     # Read the first line from the source file
 | |
|     $self->{"first_line"} = $self->{"io"}->readline;
 | |
|     # Skip empty files
 | |
|     $self->{"is_empty"} = !defined $self->{"first_line"};
 | |
|     if ($self->{"is_empty"}) {
 | |
|         print STDERR "File is empty.\n" if $VERBOSE > 1;
 | |
|         return undef;
 | |
|     }
 | |
| 
 | |
|     # Check the log file format
 | |
|     print STDERR "Checking the log file format... " if $VERBOSE > 1;
 | |
|     print STDERR "\n" if $VERBOSE > 2;
 | |
|     $_ = _private::Format->check_format($self->{"first_line"});
 | |
|     # Unrecognized log record
 | |
|     if (!defined $_) {
 | |
|         print STDERR "unknown\n" if $VERBOSE > 1;
 | |
|         die "$THIS_FILE: Unrecognized log file format";
 | |
|     }
 | |
|     print STDERR "$_\n" if $VERBOSE > 1;
 | |
|     return $_;
 | |
| }
 | |
| 
 | |
| # Create the temporary working file
 | |
| sub create_temp : method {
 | |
|     local ($_, %_);
 | |
|     my ($self, $temp, $FHT);
 | |
|     $self = $_[0];
 | |
| 
 | |
|     # Create a named temporarily working file
 | |
|     if (defined $self->{"temp"}) {
 | |
|         $temp = $self->{"temp"};
 | |
|         print STDERR "Creating $temp ... " if $VERBOSE > 2;
 | |
|         open $FHT, "+>", $temp          or die "$THIS_FILE: $temp: $!";
 | |
|         flock $FHT, LOCK_EX;
 | |
|         $self->{"FHT"} = $FHT;
 | |
|         print STDERR "done\n" if $VERBOSE > 2;
 | |
|         return $FHT;
 | |
| 
 | |
|     # Create an anonymous temporarily working file
 | |
|     } else {
 | |
|         print STDERR "Creating temporary working file for " . $self->{"file"} . " ... "
 | |
|             if $VERBOSE > 2;
 | |
|         $self->{"FHT"} = tempfile       or die "$THIS_FILE: tempfile: $!";
 | |
|         flock $self->{"FHT"}, LOCK_EX;
 | |
|         print STDERR "done\n" if $VERBOSE > 2;
 | |
|         return $self->{"FHT"};
 | |
|     }
 | |
| }
 | |
| 
 | |
| # Remove the temporary working file
 | |
| sub remove_temp : method {
 | |
|     local ($_, %_);
 | |
|     my ($self, $temp, $FHT);
 | |
|     $self = $_[0];
 | |
|     ($FHT, $temp) = ($self->{"FHT"}, $self->{"temp"});
 | |
|     # A named temporarily file
 | |
|     if (defined $self->{"temp"}) {
 | |
|         print STDERR "Removing $temp ... " if $VERBOSE > 2;
 | |
|         close $FHT                      or die "$THIS_FILE: $temp: $!";
 | |
|         unlink $temp                    or die "$THIS_FILE: $temp: $!";
 | |
|         print STDERR "done\n" if $VERBOSE > 2;
 | |
| 
 | |
|     # An anonymous temporarily working file
 | |
|     } else {
 | |
|         print STDERR "Closing temporary working file ... " if $VERBOSE > 2;
 | |
|         close $FHT                      or die "$THIS_FILE: tempfile: $!";
 | |
|         print STDERR "done\n" if $VERBOSE > 2;
 | |
|     }
 | |
|     return;
 | |
| }
 | |
| 
 | |
| # Read the source file
 | |
| sub read_source : method {
 | |
|     local ($_, %_);
 | |
|     my ($self, $file, $FHT, $count);
 | |
|     $self = $_[0];
 | |
|     ($file, $FHT) = ($self->{"file"}, $self->{"FHT"});
 | |
|     print STDERR "Reading from $file ... " if $VERBOSE > 1;
 | |
|     print STDERR "\n" if $VERBOSE > 2;
 | |
|     print STDERR "  Reading source records ... " if $VERBOSE > 2;
 | |
|     $count = 0;
 | |
|     # The first line is already read, to determine the format
 | |
|     $_ = $self->{"first_line"};
 | |
|     print $FHT $_                       or die "$THIS_FILE: tempfile: $!";
 | |
|     $count++;
 | |
|     # The rest lines
 | |
|     while (defined($_ = $self->{"io"}->readline)) {
 | |
|         print $FHT $_                   or die "$THIS_FILE: tempfile: $!";
 | |
|         $count++;
 | |
|     }
 | |
|     print STDERR "$count records\n" if $VERBOSE > 2;
 | |
|     $self->{"io"}->close($self->{"keep"}, $self->{"tmp"});
 | |
|     print STDERR "$count records\n" if $VERBOSE > 1;
 | |
|     $self->{"count"} = $count;
 | |
|     return $count;;
 | |
| }
 | |
| 
 | |
| # Read a record, returning the record and its month
 | |
| sub read_record : method {
 | |
|     local ($_, %_);
 | |
|     my ($self, $FHT);
 | |
|     $self = $_[0];
 | |
|     $FHT = $self->{"FHT"};
 | |
|     # Reset when start reading
 | |
|     if (!exists $self->{"reading_record"}) {
 | |
|         seek $FHT, 0, SEEK_SET          or die "$THIS_FILE: tempfile: $!";
 | |
|         $self->{"reading_record"} = 1;
 | |
|     }
 | |
|     $_ = <$FHT>;
 | |
|     # End of read
 | |
|     delete $self->{"reading_record"} if !defined $_;
 | |
|     return $_;
 | |
| }
 | |
| 
 | |
| # Save the records of this month
 | |
| sub save_this_month : method {
 | |
|     local ($_, %_);
 | |
|     my ($self, $record, $FH);
 | |
|     ($self, $record) = @_;
 | |
|     # Create the temporary saving space
 | |
|     if (!exists $self->{"FH_TM"}) {
 | |
|         print STDERR "\n" if $VERBOSE > 2 && defined $PROGRESS_BAR;
 | |
|         print STDERR "  Creating buffer for this month ... "
 | |
|             if $VERBOSE > 2;
 | |
|         $FH = tempfile                  or die "$THIS_FILE: tempfile: $!";
 | |
|         flock $FH, LOCK_EX              or die "$THIS_FILE: tempfile: $!";
 | |
|         print STDERR "done\n" if $VERBOSE > 2;
 | |
|         $self->{"FH_TM"} = $FH;
 | |
|         $self->{"count_this_month"} = 0;
 | |
|         $self->{"size_this_month"} = 0;
 | |
|     } else {
 | |
|         $FH = $self->{"FH_TM"};
 | |
|     }
 | |
|     # Save the record
 | |
|     print $FH $record                   or die "$THIS_FILE: tempfile: $!";
 | |
|     $self->{"count_this_month"}++;
 | |
|     $self->{"size_this_month"} += length $record;
 | |
|     return;
 | |
| }
 | |
| 
 | |
| # Return the records of this month to the log file
 | |
| sub restore_this_month : method {
 | |
|     local ($_, %_);
 | |
|     my ($self, $file, $FH);
 | |
|     $self = $_[0];
 | |
|     # Bounce if no record to restore
 | |
|     return unless exists $self->{"FH_TM"};
 | |
|     ($file, $FH) = ($self->{"file"}, $self->{"FH_TM"});
 | |
| 
 | |
|     # Prepend the records using the I/O class implementation
 | |
|     ref($self->{"io"})->prepend_records($file, $FH);
 | |
| 
 | |
|     # Report the statistics
 | |
|     printf STDERR "%s: keeping %s records, %s bytes\n",
 | |
|             $file, format_number($self->{"count_this_month"}),
 | |
|             format_number($self->{"size_this_month"})
 | |
|         if $VERBOSE > 0;
 | |
| 
 | |
|     return;
 | |
| }
 | |
| 
 | |
| # Check the source file type
 | |
| sub check_type : method {
 | |
|     local ($_, %_);
 | |
|     my ($self, $file, $FH, $PH, $CMD);
 | |
|     $self = $_[0];
 | |
|     ($file, $FH) = ($self->{"file"}, $self->{"FH"});
 | |
| 
 | |
|     # Check the file type checker to use
 | |
|     $self->check_magic;
 | |
|     die "$THIS_FILE: Cannot check STDIN from the filename suffix.\n"
 | |
|         if $self->{"stdin"} && $MAGIC_METHOD eq MAGIC_SUFFIX;
 | |
| 
 | |
|     # Check by file name suffix
 | |
|     # Check by file name suffix on empty files, too.
 | |
|     # Compress::Bzip2 2 creates empty files that confuses further processing.
 | |
|     if (-z $FH || $MAGIC_METHOD eq MAGIC_SUFFIX) {
 | |
|         return TYPE_GZIP if $file =~ /\.gz$/;
 | |
|         return TYPE_BZIP2 if $file =~ /\.bz2$/;
 | |
|         return TYPE_XZ if $file =~ /\.xz/;
 | |
|         # Otherwise we assume it to be text/plain
 | |
|         return TYPE_PLAIN;
 | |
|     }
 | |
| 
 | |
|     # Check the file format
 | |
|     # Check by File::MMagic
 | |
|     if ($MAGIC_METHOD eq MAGIC_PM) {
 | |
|         $_ = $MAGIC->checktype_filehandle($FH);
 | |
|         seek $FH, 0, SEEK_SET           or die "$THIS_FILE: $file: $!";
 | |
| 
 | |
|     # Check by the file program
 | |
|     } elsif ($MAGIC_METHOD eq MAGIC_EXEC) {
 | |
|         flock $FH, LOCK_UN;
 | |
|         @_ = ($MAGIC, $self->{"check_type"});
 | |
|         @_ = map "\"$_\"", @_ if $^O eq "MSWin32";
 | |
|         $CMD = join " ", @_;
 | |
|         # Start the process
 | |
|         if ($^O eq "MSWin32") {
 | |
|             open $PH, "$CMD |"          or die "$THIS_FILE: $CMD: $!";
 | |
|         } else {
 | |
|             open $PH, "-|", @_          or die "$THIS_FILE: $CMD: $!";
 | |
|         }
 | |
|         $_ = join "", <$PH>;
 | |
|         close $PH                       or die "$THIS_FILE: $CMD: $!";
 | |
|         if ($self->{"keep"} eq KEEP_ALL) {
 | |
|             flock $FH, LOCK_SH;
 | |
|         } else {
 | |
|             flock $FH, LOCK_EX;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     # Check the returned file type text
 | |
|     return TYPE_GZIP if /gzip/i;
 | |
|     return TYPE_BZIP2 if /bzip2/i;
 | |
|     return TYPE_XZ if /xz/i;
 | |
|     # Default everything to text/plain
 | |
|     return TYPE_PLAIN;
 | |
| }
 | |
| 
 | |
| # Check the I/O handler to use
 | |
| sub check_io : method {
 | |
|     local ($_, %_);
 | |
|     my $self;
 | |
|     $self = $_[0];
 | |
|     # We need a gzip compression I/O handler
 | |
|     return _private::IO->check_gzip if $self->{"type"} eq TYPE_GZIP;
 | |
|     # We need a bzip2 compression I/O handler
 | |
|     return _private::IO->check_bzip2 if $self->{"type"} eq TYPE_BZIP2;
 | |
|     # We need a xz compression I/O handler
 | |
|     return _private::IO->check_xz if $self->{"type"} eq TYPE_XZ;
 | |
|     # We need a plain I/O handler
 | |
|     return _private::IO::Plain->new;
 | |
| }
 | |
| 
 | |
| # Check the file type checker to use
 | |
| sub check_magic : method {
 | |
|     local ($_, %_);
 | |
|     my $self;
 | |
|     $self = $_[0];
 | |
| 
 | |
|     # Checked before
 | |
|     return $MAGIC_METHOD if defined $MAGIC_METHOD;
 | |
| 
 | |
|     print STDERR "Checking file type checker to use ... " if $VERBOSE > 1;
 | |
|     print STDERR "\n  Checking File::MMagic ... " if $VERBOSE > 2;
 | |
|     # Check if we have File::MMagic
 | |
|     if (eval { require File::MMagic; 1; }) {
 | |
|         print STDERR "OK\nfound " if $VERBOSE > 2;
 | |
|         print STDERR "File::MMagic\n" if $VERBOSE > 1;
 | |
|         $MAGIC = File::MMagic->new;
 | |
|         return ($MAGIC_METHOD = MAGIC_PM);
 | |
|     }
 | |
|     # Not found
 | |
|     print STDERR "no\n" if $VERBOSE > 2;
 | |
|     $@ =~ s/^(Can't locate \S+ in \@INC).*\n/$1\n/;     # '
 | |
|     warn "$@" if $VERBOSE == 1;
 | |
| 
 | |
|     # Looking for file from PATH
 | |
|     print STDERR "  Checking file ... " if $VERBOSE > 2;
 | |
|     # Found in PATH
 | |
|     if (defined($MAGIC = where_is "file")) {
 | |
|         print STDERR "$MAGIC\nfound " if $VERBOSE > 2;
 | |
|         print STDERR "$MAGIC\n" if $VERBOSE > 1;
 | |
|         warn "$THIS_FILE: We will check with $MAGIC instead\n"
 | |
|             if $VERBOSE > 0;
 | |
|         return ($MAGIC_METHOD = MAGIC_EXEC);
 | |
|     }
 | |
|     # Not found
 | |
|     print STDERR "no\n" if $VERBOSE > 2;
 | |
| 
 | |
|     # Check by file name suffix
 | |
|     print STDERR "  Fall back using file name suffix instead\n" if $VERBOSE > 2;
 | |
|     print STDERR "file name suffix\n" if $VERBOSE > 1;
 | |
|     warn "$THIS_FILE: We will check by file name suffix instead\n"
 | |
|         if $VERBOSE == 1;
 | |
|     return ($MAGIC_METHOD = MAGIC_SUFFIX);
 | |
| }
 | |
| 
 | |
| 
 | |
| # _private::LogFile::STDIN: The source log file as STDIN
 | |
| package _private::LogFile::STDIN;
 | |
| use 5.008;
 | |
| use strict;
 | |
| use warnings;
 | |
| use base qw(_private::LogFile);
 | |
| BEGIN {
 | |
| import main;
 | |
| }
 | |
| 
 | |
| use IO::Handle;
 | |
| use Fcntl qw(:flock :seek);
 | |
| use File::Temp qw(tempfile unlink0);
 | |
| 
 | |
| # Initialize the source log file processor
 | |
| sub new : method {
 | |
|     local ($_, %_);
 | |
|     my ($class, $self, $file, $FH, $tmp);
 | |
|     ($class, $file) = @_;
 | |
| 
 | |
|     # We only initialize STDIN
 | |
|     return $file if ref($file) ne "" || $file ne "-";
 | |
| 
 | |
|     $self = bless {}, $class;
 | |
|     $self->{"stdin"} = 1;
 | |
|     $self->{"keep"} = KEEP_ALL;
 | |
|     $self->{"override"} = OVERRIDE_OVERWRITE;
 | |
|     $self->{"tmp"} = undef;
 | |
| 
 | |
|     # Load the File::MMagic first before opening anything, or the seek
 | |
|     #   method will not be loaded into IO::Handle
 | |
|     $self->check_magic;
 | |
| 
 | |
|     # Save STDIN to somewhere
 | |
|     $file = "the STDIN buffer";
 | |
|     if ($_private::LogFile::MAGIC_METHOD eq _private::LogFile::MAGIC_EXEC) {
 | |
|         ($FH, $tmp) = tempfile(undef, UNLINK => 1)
 | |
|                                         or die "$THIS_FILE: tempfile: $!";
 | |
|         $self->{"check_type"} = $tmp;
 | |
|         $self->{"tmp"} = $tmp;
 | |
|     } else {
 | |
|         undef $tmp;
 | |
|         $FH = tempfile                  or die "$THIS_FILE: tempfile: $!";
 | |
|     }
 | |
|     ($self->{"FH"}, $self->{"file"}) = ($FH, $file);
 | |
|     flock $FH, LOCK_EX;
 | |
|     print STDERR "Saving STDIN to a buffer ... " if $VERBOSE > 1;
 | |
|     while (defined($_ = <STDIN>)) {
 | |
|         print $FH $_                    or die "$THIS_FILE: $file: $!";
 | |
|     }
 | |
|     seek $FH, 0, SEEK_SET               or die "$THIS_FILE: $file: $!";
 | |
|     print STDERR "done\n" if $VERBOSE > 1;
 | |
| 
 | |
|     # Check the file type
 | |
|     print STDERR "Checking file type of STDIN ... " if $VERBOSE > 1;
 | |
|     $self->{"type"} = $self->check_type;
 | |
|     # Unlink after check_type() with file executable
 | |
|     if ($_private::LogFile::MAGIC_METHOD eq _private::LogFile::MAGIC_EXEC) {
 | |
|         unlink0($FH, $tmp)              or die "$THIS_FILE: $tmp: $!";
 | |
|     }
 | |
|     print STDERR $self->{"type"} . "\n" if $VERBOSE > 1;
 | |
|     # Check the I/O handler to use
 | |
|     $self->{"io"} = $self->check_io;
 | |
|     # Open the file
 | |
|     $self->{"io"}->open_read($file, $self->{"FH"});
 | |
|     # Check the log file format
 | |
|     $self->{"format"} = $self->check_format;
 | |
|     # STDIN always goes to an anonymous temporarily working file
 | |
|     $self->{"temp"} = undef;
 | |
| 
 | |
|     return $self;
 | |
| }
 | |
| 
 | |
| # Remove the temporary working file
 | |
| sub remove_temp : method {
 | |
|     local ($_, %_);
 | |
|     $_ = $_[0];
 | |
|     print STDERR "Closing temporary working file for STDIN ... " if $VERBOSE > 2;
 | |
|     close $_->{"FHT"}                   or die "$THIS_FILE: tempfile: $!";
 | |
|     print STDERR "done\n" if $VERBOSE > 2;
 | |
|     return;
 | |
| }
 | |
| 
 | |
| 
 | |
| # _private::Archive: The result archived log file
 | |
| package _private::Archive;
 | |
| use 5.008;
 | |
| use strict;
 | |
| use warnings;
 | |
| BEGIN {
 | |
| import main;
 | |
| }
 | |
| 
 | |
| use Date::Parse qw(str2time);
 | |
| use Fcntl qw(:flock :seek);
 | |
| use File::Basename qw(basename);
 | |
| use File::Temp qw(tempfile);
 | |
| 
 | |
| # Initialize the result archive file processor
 | |
| sub new : method {
 | |
|     local ($_, %_);
 | |
|     my ($class, $self, $month, $file, $FH);
 | |
|     ($class, $month) = @_;
 | |
|     $self = bless {}, $class;
 | |
|     $self->{"month"} = $month;
 | |
|     $self->{"override"} = $CONF{"OVERRIDE"};
 | |
|     $self->{"format"} = $CONF{"FORMAT"};
 | |
|     if ($CONF{"COMPRESS"} eq COMPRESS_GZIP) {
 | |
|         $self->{"io"} = _private::IO->check_gzip;
 | |
|     } elsif ($CONF{"COMPRESS"} eq COMPRESS_BZIP2) {
 | |
|         $self->{"io"} = _private::IO->check_bzip2;
 | |
|     } elsif ($CONF{"COMPRESS"} eq COMPRESS_XZ) {
 | |
|         $self->{"io"} = _private::IO->check_xz;
 | |
|     } else {
 | |
|         $self->{"io"} = _private::IO::Plain->new;
 | |
|     }
 | |
|     # The resulted output file
 | |
|     $self->{"file"} = $CONF{"OUTPUT"} . "." . $month
 | |
|         . $self->{"io"}->suffix;
 | |
|     $file = $self->{"file"};
 | |
|     $self->{"ignore"} = 0;
 | |
|     # The resulted output file exists
 | |
|     if (-e $file) {
 | |
|         # If we should ask
 | |
|         # Jump off the progress bar
 | |
|         print STDERR "\n"
 | |
|             if $self->{"override"} eq OVERRIDE_ASK && defined $PROGRESS_BAR;
 | |
|         while ($self->{"override"} eq OVERRIDE_ASK) {
 | |
|             printf STDERR "$file exists, (O)overwrite, (A)append, (I)ignore, (F)fail? [F] ";
 | |
|             $_ = <STDIN>;
 | |
|             # Fail if not answered
 | |
|             if (!defined $_) {
 | |
|                 print STDERR "\nhat.. you are not here.  I had better drop it right now.\n";
 | |
|                 $self->{"override"} = OVERRIDE_FAIL;
 | |
|                 last;
 | |
|             }
 | |
|             chomp;
 | |
|             # Overwrite
 | |
|             if (lc $_ eq "o" || lc $_ eq "overwrite") {
 | |
|                 $self->{"override"} = OVERRIDE_OVERWRITE;
 | |
|             # Append
 | |
|             } elsif (lc $_ eq "a" || lc $_ eq "append") {
 | |
|                 $self->{"override"} = OVERRIDE_APPEND;
 | |
|             # Ignore
 | |
|             } elsif (lc $_ eq "i" || lc $_ eq "ignore") {
 | |
|                 $self->{"override"} = OVERRIDE_IGNORE;
 | |
|             # Fail
 | |
|             } elsif (lc $_ eq "f" || lc $_ eq "fail" || lc $_ eq "") {
 | |
|                 $self->{"override"} = OVERRIDE_FAIL;
 | |
|             # Else, ask again
 | |
|             } else {
 | |
|                 print STDERR "What?\n";
 | |
|                 $self->{"override"} = OVERRIDE_ASK;
 | |
|             }
 | |
|         }
 | |
|         # Overwrite or append
 | |
|         if (    $self->{"override"} eq OVERRIDE_OVERWRITE
 | |
|                 || $self->{"override"} eq OVERRIDE_APPEND) {
 | |
|             # OK
 | |
|         } elsif ($self->{"override"} eq OVERRIDE_IGNORE) {
 | |
|             $self->{"ignore"} = 1;
 | |
|         } elsif ($self->{"override"} eq OVERRIDE_FAIL) {
 | |
|             die "$THIS_FILE: $file: Output file exists\n";
 | |
|         }
 | |
| 
 | |
|     # Not exists - we always create it
 | |
|     } else {
 | |
|         $self->{"override"} = OVERRIDE_OVERWRITE;
 | |
|     }
 | |
|     # The temporary log record bucket
 | |
|     if (!$self->{"ignore"}) {
 | |
|         print STDERR "\n" if $VERBOSE > 2 && defined $PROGRESS_BAR;
 | |
|         print STDERR "  Creating buffer for $month ... "
 | |
|             if $VERBOSE > 2;
 | |
|         $FH = tempfile                  or die "$THIS_FILE: tempfile: $!";
 | |
|         flock $FH, LOCK_EX              or die "$THIS_FILE: tempfile: $!";
 | |
|         $self->{"FH"} = $FH;
 | |
|         print STDERR "done\n" if $VERBOSE > 2;
 | |
|     }
 | |
|     $self->{"size_original"} = 0;
 | |
|     return $self;
 | |
| }
 | |
| 
 | |
| # Add a record to the temporarily archive file
 | |
| sub add : method {
 | |
|     local ($_, %_);
 | |
|     my ($self, $FH);
 | |
|     ($self, $_) = @_;
 | |
|     $FH = $self->{"FH"};
 | |
|     print $FH $_                        or die "$THIS_FILE: tempfile: $!";
 | |
|     $self->{"size_original"} += length $_;
 | |
|     return;
 | |
| }
 | |
| 
 | |
| # Sort the records
 | |
| sub sort : method {
 | |
|     local ($_, %_);
 | |
|     my ($self, $FH0, $FH1, $month, $count, $pos, $t, @recs);
 | |
|     $self = $_[0];
 | |
|     ($FH0, $month) = ($self->{"FH"}, $self->{"month"});
 | |
| 
 | |
|     print STDERR "Sorting records of $month ... " if $VERBOSE > 1;
 | |
|     print STDERR "\n" if $VERBOSE > 2;
 | |
| 
 | |
|     # Obtain the information of each record
 | |
|     print STDERR "  Obtain the time and position of the records ... "
 | |
|         if $VERBOSE > 2;
 | |
|     seek $FH0, 0, SEEK_SET              or die "$THIS_FILE: tempfile: $!";
 | |
|     @recs = qw();
 | |
|     ($pos = tell $FH0) != -1            or die "$THIS_FILE: tempfile: $!";
 | |
|     $count = 0;
 | |
|     while (defined($_ = <$FH0>)) {
 | |
|         $t = str2time($self->{"format"}->match($_));
 | |
|         push @recs, { "pos" => $pos, "time" => $t };
 | |
|         $count++;
 | |
|         ($pos = tell $FH0) != -1        or die "$THIS_FILE: tempfile: $!";
 | |
|     }
 | |
|     print STDERR "$count records\n" if $VERBOSE > 2;
 | |
| 
 | |
|     # Sort by time and then original order
 | |
|     print STDERR "  Sorting the records by time ... " if $VERBOSE > 2;
 | |
|     @recs = CORE::sort {  $$a{"time"} <=> $$b{"time"}
 | |
|                     || $$a{"pos"} <=> $$b{"pos"} } @recs;
 | |
|     print STDERR "done\n" if $VERBOSE > 2;
 | |
| 
 | |
|     # Store the records according to the new order
 | |
|     print STDERR "  Creating new buffer for $month ... " if $VERBOSE > 2;
 | |
|     $FH1 = tempfile                     or die "$THIS_FILE: tempfile: $!";
 | |
|     flock $FH1, LOCK_EX                 or die "$THIS_FILE: tempfile: $!";
 | |
|     print STDERR "done\n" if $VERBOSE > 2;
 | |
|     print STDERR "  Storing sorted records to the new buffer ... "
 | |
|         if $VERBOSE > 2;
 | |
|     $count = 0;
 | |
|     foreach my $r (@recs) {
 | |
|         seek $FH0, $$r{"pos"}, SEEK_SET or die "$THIS_FILE: tempfile: $!";
 | |
|         $_ = <$FH0>;
 | |
|         print $FH1 $_                   or die "$THIS_FILE: tempfile: $!";
 | |
|         $count++;
 | |
|     }
 | |
|     print STDERR "$count records\n" if $VERBOSE > 2;
 | |
| 
 | |
|     # Use the new buffer instead of the old one
 | |
|     print STDERR "  Switching to the new buffer ... " if $VERBOSE > 2;
 | |
|     flock $FH0, LOCK_UN                 or die "$THIS_FILE: tempfile: $!";
 | |
|     close $FH0                          or die "$THIS_FILE: tempfile: $!";
 | |
|     $self->{"FH"} = $FH1;
 | |
|     print STDERR "done\n" if $VERBOSE > 2;
 | |
| 
 | |
|     print STDERR "$count records\n" if $VERBOSE > 1;
 | |
|     return;
 | |
| }
 | |
| 
 | |
| # Store the archived log records
 | |
| sub store_archive : method {
 | |
|     local ($_, %_);
 | |
|     my ($self, $FH, $file, $count);
 | |
|     $self = $_[0];
 | |
|     ($FH, $file) = ($self->{"FH"}, $self->{"file"});
 | |
|     # Reset the file reader
 | |
|     seek $FH, 0, SEEK_SET               or die "$THIS_FILE: tempfile: $!";
 | |
|     # Overwrite
 | |
|     if ($self->{"override"} eq OVERRIDE_OVERWRITE) {
 | |
|         print STDERR "Writing to $file ... " if $VERBOSE > 1;
 | |
|         print STDERR "\n" if $VERBOSE > 2;
 | |
|         $self->{"io"}->open_write($file);
 | |
|     # Append
 | |
|     } elsif ($self->{"override"} eq OVERRIDE_APPEND) {
 | |
|         print STDERR "Appending to $file ... " if $VERBOSE > 1;
 | |
|         print STDERR "\n" if $VERBOSE > 2;
 | |
|         $self->{"size_compressed"} = (stat $file)[7];
 | |
|         $self->{"io"}->open_append($file);
 | |
|     }
 | |
|     # Copy the data to the archive file
 | |
|     print STDERR "  Writing records ... " if $VERBOSE > 2;
 | |
|     $count = 0;
 | |
|     while (defined($_ = <$FH>)) {
 | |
|         $self->{"io"}->write($_);
 | |
|         $count++;
 | |
|     }
 | |
|     print STDERR "$count records\n" if $VERBOSE > 2;
 | |
|     $self->{"io"}->close;
 | |
|     print STDERR "$count records\n" if $VERBOSE > 1;
 | |
|     # Report the statistics
 | |
|     # Overwrite
 | |
|     if ($self->{"override"} eq OVERRIDE_OVERWRITE) {
 | |
|         $self->{"size_compressed"} = (stat $file)[7];
 | |
|         printf STDERR "%s: writing %s records, %s bytes, %s bytes, %0.2f%%\n",
 | |
|                 $self->{"month"}, format_number($count),
 | |
|                 format_number($self->{"size_original"}),
 | |
|                 format_number($self->{"size_compressed"}),
 | |
|                 ($self->{"size_compressed"}*100/$self->{"size_original"})
 | |
|             if $VERBOSE > 0;
 | |
| 
 | |
|     # Append
 | |
|     } elsif ($self->{"override"} eq OVERRIDE_APPEND) {
 | |
|         $self->{"size_compressed"} = (stat $file)[7] - $self->{"size_compressed"};
 | |
|         printf STDERR "%s: adding  %s records, %s bytes, %s bytes, %0.2f%%\n",
 | |
|                 $self->{"month"}, format_number($count),
 | |
|                 format_number($self->{"size_original"}),
 | |
|                 format_number($self->{"size_compressed"}),
 | |
|                 ($self->{"size_compressed"}*100/$self->{"size_original"})
 | |
|             if $VERBOSE > 0;
 | |
|     }
 | |
|     return;
 | |
| }
 | |
| 
 | |
| # _private::IO: The abstract I/O handler interface
 | |
| package _private::IO;
 | |
| use 5.008;
 | |
| use strict;
 | |
| use warnings;
 | |
| BEGIN {
 | |
| import main;
 | |
| }
 | |
| 
 | |
| use Fcntl qw(:seek);
 | |
| 
 | |
| our ($GZIP_IO, $BZIP2_IO, $XZ_IO);
 | |
| BEGIN {
 | |
| undef $GZIP_IO;
 | |
| undef $BZIP2_IO;
 | |
| undef $XZ_IO;
 | |
| }
 | |
| 
 | |
| # Initialize the I/O handler interface
 | |
| sub new : method { bless {}, $_[0]; }
 | |
| 
 | |
| # The file name suffix of this mime type
 | |
| sub suffix : method { ""; }
 | |
| 
 | |
| # Check for compression method of gzip
 | |
| sub check_gzip : method {
 | |
|     local ($_, %_);
 | |
| 
 | |
|     # Checked before
 | |
|     return ref($GZIP_IO)->new if defined $GZIP_IO;
 | |
| 
 | |
|     # See whether Compress::Zlib or gzip
 | |
|     print STDERR "Checking gzip I/O handler to use ... " if $VERBOSE > 1;
 | |
|     print STDERR "\n  Checking Compress::Zlib ... " if $VERBOSE > 2;
 | |
|     # Check if we have Compress::Zlib
 | |
|     if (eval { require Compress::Zlib; 1; }) {
 | |
|         print STDERR "OK\nfound " if $VERBOSE > 2;
 | |
|         print STDERR "Compress::Zlib\n" if $VERBOSE > 1;
 | |
|         return ($GZIP_IO = _private::IO::Gzip::PM->new);
 | |
|     }
 | |
|     # Not found
 | |
|     print STDERR "no\n" if $VERBOSE > 2;
 | |
|     # It's OK not to warn
 | |
| 
 | |
|     # Looking for gzip from PATH
 | |
|     print STDERR "  Checking gzip... " if $VERBOSE > 2;
 | |
|     # Found in PATH
 | |
|     if (defined($_ = where_is "gzip")) {
 | |
|         print STDERR "$_\nfound " if $VERBOSE > 2;
 | |
|         print STDERR "$_\n" if $VERBOSE > 1;
 | |
|         return ($GZIP_IO = _private::IO::Gzip::Exec->new);
 | |
|     }
 | |
|     # Not found
 | |
|     print STDERR "no\n" if $VERBOSE > 2;
 | |
| 
 | |
|     print STDERR "not found\n" if $VERBOSE > 1;
 | |
|     die "$THIS_FILE: Necessary Compress::Zlib or gzip not available.\n$SHORT_HELP\n";
 | |
| }
 | |
| 
 | |
| # Check for compression method of bzip2
 | |
| sub check_bzip2 : method {
 | |
|     local ($_, %_);
 | |
| 
 | |
|     # Checked before
 | |
|     return ref($BZIP2_IO)->new if defined $BZIP2_IO;
 | |
| 
 | |
|     # See whether Compress::Bzip2 or bzip2
 | |
|     print STDERR "Checking bzip2 I/O handler to use ... " if $VERBOSE > 1;
 | |
|     print STDERR "\n  Checking Compress::Bzip2 ... " if $VERBOSE > 2;
 | |
|     # Check if we have Compress::Bzip2
 | |
|     if (eval { require Compress::Bzip2; import Compress::Bzip2 2.00; 1; }) {
 | |
|         print STDERR "OK\nfound " if $VERBOSE > 2;
 | |
|         print STDERR "Compress::Bzip2\n" if $VERBOSE > 1;
 | |
|         return ($BZIP2_IO = _private::IO::Bzip2::PM->new);
 | |
|     }
 | |
|     # Not found
 | |
|     print STDERR "no\n" if $VERBOSE > 2;
 | |
|     # It's OK not to warn
 | |
| 
 | |
|     # Looking for bzip2 from PATH
 | |
|     print STDERR "  Checking bzip2... " if $VERBOSE > 2;
 | |
|     # Found in PATH
 | |
|     if (defined($_ = where_is "bzip2")) {
 | |
|         print STDERR "$_\nfound " if $VERBOSE > 2;
 | |
|         print STDERR "$_\n" if $VERBOSE > 1;
 | |
|         return ($BZIP2_IO = _private::IO::Bzip2::Exec->new);
 | |
|     }
 | |
|     # Not found
 | |
|     print STDERR "no\n" if $VERBOSE > 2;
 | |
| 
 | |
|     print STDERR "not found\n" if $VERBOSE > 1;
 | |
|     die "$THIS_FILE: Necessary Compress::Bzip2 or bzip2 not available.\n$SHORT_HELP\n";
 | |
| }
 | |
| 
 | |
| # Check for compression method of xz
 | |
| sub check_xz : method {
 | |
|     local ($_, %_);
 | |
| 
 | |
|     # Checked before
 | |
|     return ref($XZ_IO)->new if defined $XZ_IO;
 | |
| 
 | |
|     # See whether IO::Compress::Xz or xz
 | |
|     print STDERR "Checking xz I/O handler to use ... " if $VERBOSE > 1;
 | |
|     print STDERR "\n  Checking IO::Compress::Xz ... " if $VERBOSE > 2;
 | |
|     # Check if we have IO::Compress::Xz
 | |
|     if (eval { require IO::Compress::Xz; require IO::Uncompress::UnXz; 1; }) {
 | |
|         print STDERR "OK\nfound " if $VERBOSE > 2;
 | |
|         print STDERR "IO::Compress::Xz\n" if $VERBOSE > 1;
 | |
|         return ($XZ_IO = _private::IO::Xz::PM->new);
 | |
|     }
 | |
|     # Not found
 | |
|     print STDERR "no\n" if $VERBOSE > 2;
 | |
|     # It's OK not to warn
 | |
| 
 | |
|     # Looking for xz from PATH
 | |
|     print STDERR "  Checking xz... " if $VERBOSE > 2;
 | |
|     # Found in PATH
 | |
|     if (defined($_ = where_is "xz")) {
 | |
|         print STDERR "$_\nfound " if $VERBOSE > 2;
 | |
|         print STDERR "$_\n" if $VERBOSE > 1;
 | |
|         return ($XZ_IO = _private::IO::Xz::Exec->new);
 | |
|     }
 | |
|     # Not found
 | |
|     print STDERR "no\n" if $VERBOSE > 2;
 | |
| 
 | |
|     print STDERR "not found\n" if $VERBOSE > 1;
 | |
|     die "$THIS_FILE: Necessary IO::Compress::Xz or xz not available.\n$SHORT_HELP\n";
 | |
| }
 | |
| 
 | |
| # Prepend records to an existing file
 | |
| #   * static method *
 | |
| #   For most I/O we read records out and write back with 2 I/O accesses.
 | |
| #   But for plain text we need only open the file once.
 | |
| #   This implementation is for most I/O.  Plain text implement this itself.
 | |
| sub prepend_records : method {
 | |
|     local ($_, %_);
 | |
|     my ($class, $file, $FHT, $io, $count);
 | |
|     ($class, $file, $FHT) = @_;
 | |
| 
 | |
|     # Read the current records (added after program execution)
 | |
|     $io = $class->new;
 | |
|     print STDERR "Reading new records from $file ... " if $VERBOSE > 1;
 | |
|     print STDERR "\n" if $VERBOSE > 2;
 | |
|     $io->open_read($file);
 | |
|     print STDERR "  Reading new records ... " if $VERBOSE > 2;
 | |
|     $count = 0;
 | |
|     while (defined($_ = $io->readline)) {
 | |
|         print $FHT $_                   or die "$THIS_FILE: tempfile: $!";
 | |
|         $count++;
 | |
|     }
 | |
|     print STDERR "$count records\n" if $VERBOSE > 2;
 | |
|     $io->close;
 | |
|     print STDERR "$count records\n" if $VERBOSE > 1;
 | |
| 
 | |
|     # Returning all the records
 | |
|     # Start a new I/O handler of the same class
 | |
|     seek $FHT, 0, SEEK_SET              or die "$THIS_FILE: tempfile: $!";
 | |
|     $io = $class->new;
 | |
|     print STDERR "Returning all records to $file ... " if $VERBOSE > 1;
 | |
|     print STDERR "\n" if $VERBOSE > 2;
 | |
|     $io->open_write($file);
 | |
|     print STDERR "  Writing records ... " if $VERBOSE > 2;
 | |
|     $count = 0;
 | |
|     while (defined($_ = <$FHT>)) {
 | |
|         $io->write($_);
 | |
|         $count++;
 | |
|     }
 | |
|     print STDERR "$count records\n" if $VERBOSE > 2;
 | |
|     $io->close;
 | |
|     print STDERR "$count records\n" if $VERBOSE > 1;
 | |
| 
 | |
|     return;
 | |
| }
 | |
| 
 | |
| 
 | |
| # _private::IO::Plain: The plain I/O handler
 | |
| package _private::IO::Plain;
 | |
| use 5.008;
 | |
| use strict;
 | |
| use warnings;
 | |
| use base qw(_private::IO);
 | |
| BEGIN {
 | |
| import main;
 | |
| }
 | |
| 
 | |
| use Fcntl qw(:flock :seek);
 | |
| 
 | |
| # Open the file for reading
 | |
| sub open_read : method {
 | |
|     local ($_, %_);
 | |
|     my ($self, $file, $FH);
 | |
|     ($self, $file, $FH) = @_;
 | |
|     # Open the file if it is not opened yet
 | |
|     if (!defined $FH) {
 | |
|         print STDERR "  Opening file in read mode ... " if $VERBOSE > 2;
 | |
|         open $FH, "+<", $file           or die "$THIS_FILE: $file: $!";
 | |
|         flock $FH, LOCK_EX;
 | |
|         print STDERR "done\n" if $VERBOSE > 2;
 | |
|     }
 | |
|     ($self->{"file"}, $self->{"FH"}) = ($file, $FH);
 | |
|     return;
 | |
| }
 | |
| 
 | |
| # Open the file for writing
 | |
| sub open_write : method {
 | |
|     local ($_, %_);
 | |
|     my ($self, $file, $FH);
 | |
|     ($self, $file, $FH) = @_;
 | |
|     # Open the file if it is not opened yet
 | |
|     if (!defined $FH) {
 | |
|         print STDERR "  Creating file in write mode ... " if $VERBOSE > 2;
 | |
|         open $FH, "+>", $file           or die "$THIS_FILE: $file: $!";
 | |
|         flock $FH, LOCK_EX;
 | |
|         print STDERR "done\n" if $VERBOSE > 2;
 | |
|     }
 | |
|     ($self->{"file"}, $self->{"FH"}) = ($file, $FH);
 | |
|     return;
 | |
| }
 | |
| 
 | |
| # Open the file for appending
 | |
| sub open_append : method {
 | |
|     local ($_, %_);
 | |
|     my ($self, $file, $FH);
 | |
|     ($self, $file, $FH) = @_;
 | |
|     # Open the file if it is not opened yet
 | |
|     if (!defined $FH) {
 | |
|         print STDERR "  Opening file in append mode ... " if $VERBOSE > 2;
 | |
|         open $FH, ">>", $file           or die "$THIS_FILE: $file: $!";
 | |
|         flock $FH, LOCK_EX;
 | |
|         print STDERR "done\n" if $VERBOSE > 2;
 | |
|     }
 | |
|     ($self->{"file"}, $self->{"FH"}) = ($file, $FH);
 | |
|     return;
 | |
| }
 | |
| 
 | |
| # Read a line from the I/O stream
 | |
| sub readline : method {
 | |
|     local ($_, %_);
 | |
|     my ($self, $FH);
 | |
|     $self = $_[0];
 | |
|     $FH = $self->{"FH"};
 | |
|     return <$FH>;
 | |
| }
 | |
| 
 | |
| # Output data to the I/O stream
 | |
| sub write : method {
 | |
|     local ($_, %_);
 | |
|     my ($self, $file, $FH);
 | |
|     ($self, $_) = @_;
 | |
|     ($file, $FH) = ($self->{"file"}, $self->{"FH"});
 | |
|     print $FH $_                        or die "$THIS_FILE: $file: $!";
 | |
|     return;
 | |
| }
 | |
| 
 | |
| # Close the I/O stream
 | |
| sub close : method {
 | |
|     local ($_, %_);
 | |
|     my ($self, $keep, $tmp, $file, $FH);
 | |
|     ($self, $keep, $tmp) = @_;
 | |
|     $keep = KEEP_ALL if @_ < 2;
 | |
|     ($file, $FH) = ($self->{"file"}, $self->{"FH"});
 | |
| 
 | |
|     # Restart the file
 | |
|     if ($keep eq KEEP_RESTART || $keep eq KEEP_THIS_MONTH) {
 | |
|         # Empty the source file
 | |
|         print STDERR "  Emptying file ... " if $VERBOSE > 2;
 | |
|         seek $FH, 0, SEEK_SET           or die "$THIS_FILE: $file: $!";
 | |
|         truncate $FH, 0                 or die "$THIS_FILE: $file: $!";
 | |
|         print STDERR "done\n" if $VERBOSE > 2;
 | |
|     }
 | |
| 
 | |
|     CORE::close $FH                     or die "$THIS_FILE: $file: $!";
 | |
|     delete $self->{"FH"};
 | |
|     delete $self->{"file"};
 | |
| 
 | |
|     # Delete the file
 | |
|     if ($keep eq KEEP_DELETE) {
 | |
|         print STDERR "  Deleting file ... " if $VERBOSE > 2;
 | |
|         unlink $file                    or die "$THIS_FILE: $file: $!";
 | |
|         print STDERR "done\n" if $VERBOSE > 2;
 | |
|     }
 | |
|     # Delete the temporary file if needed
 | |
|     if (defined $tmp && -e $tmp) {
 | |
|         unlink $tmp                     or die "$THIS_FILE: $tmp: $!";
 | |
|     }
 | |
|     return;
 | |
| }
 | |
| 
 | |
| # Prepend records to an existing file
 | |
| #   * static method *
 | |
| #   Plain text version that only open the file once
 | |
| sub prepend_records : method {
 | |
|     local ($_, %_);
 | |
|     my ($class, $file, $FHT, $FHC, $count);
 | |
|     ($class, $file, $FHT) = @_;
 | |
| 
 | |
|     # Read the current records (added after program execution)
 | |
|     print STDERR "Reading new records from $file ... " if $VERBOSE > 1;
 | |
|     print STDERR "\n" if $VERBOSE > 2;
 | |
| 
 | |
|     print STDERR "  Opening file in read/write mode ... " if $VERBOSE > 2;
 | |
|     open $FHC, "+<", $file              or die "$THIS_FILE: $file: $!";
 | |
|     flock $FHC, LOCK_EX;
 | |
|     print STDERR "done\n" if $VERBOSE > 2;
 | |
| 
 | |
|     # Read the new records
 | |
|     print STDERR "  Reading new records ... " if $VERBOSE > 2;
 | |
|     $count = 0;
 | |
|     while (defined($_ = <$FHC>)) {
 | |
|         print $FHT $_                   or die "$THIS_FILE: tempfile: $!";
 | |
|         $count++;
 | |
|     }
 | |
|     print STDERR "$count records\n" if $VERBOSE > 2;
 | |
| 
 | |
|     # Reset the reader/writer
 | |
|     seek $FHT, 0, SEEK_SET              or die "$THIS_FILE: tempfile: $!";
 | |
|     seek $FHC, 0, SEEK_SET              or die "$THIS_FILE: $file: $!";
 | |
|     truncate $FHC, 0                    or die "$THIS_FILE: $file: $!";
 | |
| 
 | |
|     # Return all the records
 | |
|     print STDERR "  Writing records ... " if $VERBOSE > 2;
 | |
|     $count = 0;
 | |
|     while (defined($_ = <$FHT>)) {
 | |
|         print $FHC $_                   or die "$THIS_FILE: $file: $!";
 | |
|         $count++;
 | |
|     }
 | |
|     print STDERR "$count records\n" if $VERBOSE > 2;
 | |
| 
 | |
|     CORE::close $FHC                    or die "$THIS_FILE: $file: $!";
 | |
|     print STDERR "$count records\n" if $VERBOSE > 1;
 | |
| 
 | |
|     return;
 | |
| }
 | |
| 
 | |
| 
 | |
| # _private::IO::Gzip::PM: The gzip module compression I/O handler
 | |
| package _private::IO::Gzip::PM;
 | |
| use 5.008;
 | |
| use strict;
 | |
| use warnings;
 | |
| use base qw(_private::IO);
 | |
| BEGIN {
 | |
| import main;
 | |
| }
 | |
| 
 | |
| use Fcntl qw(:flock :seek);
 | |
| use File::Temp qw(tempfile);
 | |
| 
 | |
| # The file name suffix of this mime type
 | |
| sub suffix : method { ".gz"; }
 | |
| 
 | |
| # Open the file for reading
 | |
| sub open_read : method {
 | |
|     local ($_, %_);
 | |
|     my ($self, $file, $FH);
 | |
|     ($self, $file, $FH) = @_;
 | |
|     # Open the file if it is not opened yet
 | |
|     if (!defined $FH) {
 | |
|         print STDERR "  Opening file in read mode ... " if $VERBOSE > 2;
 | |
|         open $FH, "+<", $file           or die "$THIS_FILE: $file: $!";
 | |
|         binmode $FH                     or die "$THIS_FILE: $file: $!";
 | |
|         flock $FH, LOCK_EX;
 | |
|         print STDERR "done\n" if $VERBOSE > 2;
 | |
|     }
 | |
|     ($self->{"file"}, $self->{"FH"}) = ($file, $FH);
 | |
|     import Compress::Zlib qw(gzopen);
 | |
|     print STDERR "  Attaching file with gzopen(..., \"rb\") ... " if $VERBOSE > 2;
 | |
|     $self->{"gz"} = gzopen($FH, "rb")   or die "$THIS_FILE: $file: $!";
 | |
|     print STDERR "done\n" if $VERBOSE > 2;
 | |
|     return;
 | |
| }
 | |
| 
 | |
| # Open the file for writing
 | |
| sub open_write : method {
 | |
|     local ($_, %_);
 | |
|     my ($self, $file, $FH);
 | |
|     ($self, $file, $FH) = @_;
 | |
|     # Open the file if it is not opened yet
 | |
|     if (!defined $FH) {
 | |
|         print STDERR "  Creating file in write mode ... " if $VERBOSE > 2;
 | |
|         open $FH, "+>", $file           or die "$THIS_FILE: $file: $!";
 | |
|         binmode $FH                     or die "$THIS_FILE: $file: $!";
 | |
|         flock $FH, LOCK_EX;
 | |
|         print STDERR "done\n" if $VERBOSE > 2;
 | |
|     }
 | |
|     ($self->{"file"}, $self->{"FH"}) = ($file, $FH);
 | |
|     import Compress::Zlib qw(gzopen);
 | |
|     print STDERR "  Attaching file with gzopen(..., \"wb9\") ... " if $VERBOSE > 2;
 | |
|     $self->{"gz"} = gzopen($FH, "wb9")  or die "$THIS_FILE: $file: $!";
 | |
|     print STDERR "done\n" if $VERBOSE > 2;
 | |
|     return;
 | |
| }
 | |
| 
 | |
| # Open the file for appending
 | |
| sub open_append : method {
 | |
|     local ($_, %_);
 | |
|     my ($self, $file, $FH, $gz);
 | |
|     ($self, $file, $FH) = @_;
 | |
|     # Open the file if it is not opened yet
 | |
|     if (!defined $FH) {
 | |
|         print STDERR "  Opening file in read/write mode ... " if $VERBOSE > 2;
 | |
|         open $FH, "+<", $file           or die "$THIS_FILE: $file: $!";
 | |
|         binmode $FH                     or die "$THIS_FILE: $file: $!";
 | |
|         flock $FH, LOCK_EX;
 | |
|         print STDERR "done\n" if $VERBOSE > 2;
 | |
|     }
 | |
|     ($self->{"file"}, $self->{"FH"}) = ($file, $FH);
 | |
|     import Compress::Zlib qw(gzopen);
 | |
| 
 | |
|     # Save the original data if file has content so that file size is
 | |
|     # greater than 0.  STDOUT is always of size 0.
 | |
|     if ((stat $FH)[7] > 0) {
 | |
|         my ($count, $FHT, $gzt, $n);
 | |
|         seek $FH, 0, SEEK_SET           or die "$THIS_FILE: $file: $!";
 | |
|         # Copy the original content to a buffer
 | |
|         print STDERR "  Reading compressed data to the buffer ... " if $VERBOSE > 2;
 | |
|         $FHT = tempfile                 or die "$THIS_FILE: tempfile: $!";
 | |
|         while (defined($_ = <$FH>)) {
 | |
|             print $FHT $_               or die "$THIS_FILE: tempfile: $!";
 | |
|         }
 | |
|         print STDERR "done\n" if $VERBOSE > 2;
 | |
|         print STDERR "  Restarting file ... " if $VERBOSE > 2;
 | |
|         seek $FHT, 0, SEEK_SET          or die "$THIS_FILE: tempfile: $!";
 | |
|         seek $FH, 0, SEEK_SET           or die "$THIS_FILE: $file: $!";
 | |
|         truncate $FH, 0                 or die "$THIS_FILE: $file: $!";
 | |
|         print STDERR "done\n" if $VERBOSE > 2;
 | |
| 
 | |
|         # Decompress the buffer and save to our file
 | |
|         print STDERR "  Attaching buffer with gzopen(..., \"rb\") ... " if $VERBOSE > 2;
 | |
|         $gzt = gzopen($FHT, "rb")       or die "$THIS_FILE: tempfile: $!";
 | |
|         print STDERR "done\n" if $VERBOSE > 2;
 | |
|         print STDERR "  Attaching file with gzopen(..., \"wb9\") ... " if $VERBOSE > 2;
 | |
|         $gz = gzopen($FH, "wb9")        or die "$THIS_FILE: $file: $!";
 | |
|         print STDERR "done\n" if $VERBOSE > 2;
 | |
| 
 | |
|         print STDERR "  Reading old records back from the buffer ... " if $VERBOSE > 2;
 | |
|         $count = 0;
 | |
|         while (($n = $gzt->gzreadline($_)) != 0) {
 | |
|             die "$THIS_FILE: tempfile: " . $gz->gzerror if $n == -1;
 | |
|             ($gz->gzwrite($_) == $n)    or die "$THIS_FILE: $file: " . $gz->gzerror;
 | |
|             $count++;
 | |
|         }
 | |
|         close $FHT                      or die "$THIS_FILE: tempfile: $!";
 | |
|         print STDERR "$count records\n" if $VERBOSE > 2;
 | |
| 
 | |
|     # A whole new file
 | |
|     } else {
 | |
|         print STDERR "  Attaching file with gzopen(..., \"wb9\") ... " if $VERBOSE > 2;
 | |
|         $gz = gzopen($FH, "wb9")        or die "$THIS_FILE: $file: $!";
 | |
|         print STDERR "done\n" if $VERBOSE > 2;
 | |
|     }
 | |
| 
 | |
|     $self->{"gz"} = $gz;
 | |
|     return;
 | |
| }
 | |
| 
 | |
| # Read a line from the I/O stream
 | |
| sub readline : method {
 | |
|     local ($_, %_);
 | |
|     my ($self, $file, $gz, $n);
 | |
|     $self = $_[0];
 | |
|     ($file, $gz) = ($self->{"file"}, $self->{"gz"});
 | |
|     (($n = $gz->gzreadline($_)) != -1)  or die "$THIS_FILE: $file: " . $gz->gzerror;
 | |
|     return undef if $n == 0;
 | |
|     return $_;
 | |
| }
 | |
| 
 | |
| # Output data to the I/O stream
 | |
| sub write : method {
 | |
|     local ($_, %_);
 | |
|     my ($self, $file, $gz);
 | |
|     ($self, $_) = @_;
 | |
|     ($file, $gz) = ($self->{"file"}, $self->{"gz"});
 | |
|     ($gz->gzwrite($_) == length $_)     or die "$THIS_FILE: $file: " . $gz->gzerror;
 | |
|     return;
 | |
| }
 | |
| 
 | |
| # Close the I/O stream
 | |
| sub close : method {
 | |
|     local ($_, %_);
 | |
|     my ($self, $keep, $tmp, $file, $FH, $gz);
 | |
|     ($self, $keep, $tmp) = @_;
 | |
|     $keep = KEEP_ALL if @_ < 2;
 | |
|     ($file, $FH, $gz) = ($self->{"file"}, $self->{"FH"}, $self->{"gz"});
 | |
| 
 | |
|     # Restart the file
 | |
|     if ($keep eq KEEP_RESTART || $keep eq KEEP_THIS_MONTH) {
 | |
|         # Empty the source file
 | |
|         print STDERR "  Emptying file ... " if $VERBOSE > 2;
 | |
|         seek $FH, 0, SEEK_SET           or die "$THIS_FILE: $file: $!";
 | |
|         truncate $FH, 0                 or die "$THIS_FILE: $file: $!";
 | |
|         print STDERR "done\n" if $VERBOSE > 2;
 | |
| 
 | |
|         # Create empty compressed content
 | |
|         print STDERR "  Applying empty compressed content ... " if $VERBOSE > 2;
 | |
|         $_ = gzopen($FH, "wb9")         or die "$THIS_FILE: $file: $!";
 | |
|         $_->gzclose                     and die "$THIS_FILE: $file: " . $_->gzerror;
 | |
|         undef $_;
 | |
|         undef $gz;
 | |
|         print STDERR "done\n" if $VERBOSE > 2;
 | |
|     }
 | |
| 
 | |
|     if (defined $gz) {
 | |
|         $gz->gzclose                    and die "$THIS_FILE: $file: " . $gz->gzerror;
 | |
|     }
 | |
|     CORE::close $self->{"FH"} if $self->{"FH"}->opened;
 | |
|     delete $self->{"gz"};
 | |
|     delete $self->{"FH"};
 | |
|     delete $self->{"file"};
 | |
| 
 | |
|     # Delete the file
 | |
|     if ($keep eq KEEP_DELETE) {
 | |
|         print STDERR "  Deleting file ... " if $VERBOSE > 2;
 | |
|         unlink $file                    or die "$THIS_FILE: $file: $!";
 | |
|         print STDERR "done\n" if $VERBOSE > 2;
 | |
|     }
 | |
|     # Delete the temporary file if needed
 | |
|     if (defined $tmp && -e $tmp) {
 | |
|         unlink $tmp                     or die "$THIS_FILE: $tmp: $!";
 | |
|     }
 | |
|     return;
 | |
| }
 | |
| 
 | |
| 
 | |
| # _private::IO::Gzip::Exec: The gzip executable compression I/O handler
 | |
| package _private::IO::Gzip::Exec;
 | |
| use 5.008;
 | |
| use strict;
 | |
| use warnings;
 | |
| use base qw(_private::IO);
 | |
| BEGIN {
 | |
| import main;
 | |
| }
 | |
| 
 | |
| use Fcntl qw(:flock :seek);
 | |
| use File::Temp qw(tempfile);
 | |
| 
 | |
| our ($EXEC);
 | |
| BEGIN {
 | |
| undef $EXEC;
 | |
| }
 | |
| 
 | |
| # The file name suffix of this mime type
 | |
| sub suffix : method { ".gz"; }
 | |
| 
 | |
| # Open the file for reading
 | |
| sub open_read : method {
 | |
|     local ($_, %_);
 | |
|     my ($self, $file, $FH, $PH, $CMD);
 | |
|     ($self, $file, $FH) = @_;
 | |
|     # Open the file if it is not opened yet
 | |
|     if (!defined $FH) {
 | |
|         print STDERR "  Opening file in read mode ... " if $VERBOSE > 2;
 | |
|         open $FH, "+<", $file           or die "$THIS_FILE: $file: $!";
 | |
|         binmode $FH                     or die "$THIS_FILE: $file: $!";
 | |
|         print STDERR "done\n" if $VERBOSE > 2;
 | |
|     } else {
 | |
|         flock $FH, LOCK_UN;
 | |
|     }
 | |
|     ($self->{"file"}, $self->{"FH"}) = ($file, $FH);
 | |
|     $EXEC = where_is "gzip" if !defined $EXEC;
 | |
| 
 | |
|     @_ = ($EXEC, "-cdf");
 | |
|     @_ = map "\"$_\"", @_ if $^O eq "MSWin32";
 | |
|     $CMD = join " ", @_;
 | |
|     print STDERR "  Starting $CMD from file ... " if $VERBOSE > 2;
 | |
|     # Redirect STDIN to $FH
 | |
|     open STDIN, "<&", $FH               or die "$THIS_FILE: $file: $!";
 | |
|     # Start the process
 | |
|     if ($^O eq "MSWin32") {
 | |
|         open $PH, "$CMD |"              or die "$THIS_FILE: $CMD: $!";
 | |
|     } else {
 | |
|         open $PH, "-|", @_              or die "$THIS_FILE: $CMD: $!";
 | |
|     }
 | |
|     # Restore STDIN
 | |
|     open STDIN, "<&", $STDIN            or die "$THIS_FILE: STDIN: $!";
 | |
|     print STDERR "done\n" if $VERBOSE > 2;
 | |
|     ($self->{"CMD"}, $self->{"PH"}) = ([@_], $PH);
 | |
|     return;
 | |
| }
 | |
| 
 | |
| # Open the file for writing
 | |
| sub open_write : method {
 | |
|     local ($_, %_);
 | |
|     my ($self, $file, $FH, $PH, $CMD);
 | |
|     ($self, $file, $FH) = @_;
 | |
|     # Open the file if it is not opened yet
 | |
|     if (!defined $FH) {
 | |
|         print STDERR "  Creating file in write mode ... " if $VERBOSE > 2;
 | |
|         open $FH, "+>", $file           or die "$THIS_FILE: $file: $!";
 | |
|         binmode $FH                     or die "$THIS_FILE: $file: $!";
 | |
|         print STDERR "done\n" if $VERBOSE > 2;
 | |
|     } else {
 | |
|         flock $FH, LOCK_UN;
 | |
|     }
 | |
|     ($self->{"file"}, $self->{"FH"}) = ($file, $FH);
 | |
|     $EXEC = where_is "gzip" if !defined $EXEC;
 | |
| 
 | |
|     @_ = ($EXEC, "-c9f");
 | |
|     @_ = map "\"$_\"", @_ if $^O eq "MSWin32";
 | |
|     $CMD = join " ", @_;
 | |
|     print STDERR "  Starting $CMD to file ... " if $VERBOSE > 2;
 | |
|     # Redirect STDOUT to $FH
 | |
|     open STDOUT, ">&", $FH              or die "$THIS_FILE: $file: $!";
 | |
|     # Start the process
 | |
|     if ($^O eq "MSWin32") {
 | |
|         open $PH, "| $CMD"              or die "$THIS_FILE: $CMD: $!";
 | |
|     } else {
 | |
|         open $PH, "|-", @_              or die "$THIS_FILE: $CMD: $!";
 | |
|     }
 | |
|     # Restore STDOUT
 | |
|     open STDOUT, ">&", $STDOUT          or die "$THIS_FILE: STDOUT: $!";
 | |
|     print STDERR "done\n" if $VERBOSE > 2;
 | |
|     ($self->{"CMD"}, $self->{"PH"}) = ([@_], $PH);
 | |
|     return;
 | |
| }
 | |
| 
 | |
| # Open the file for appending
 | |
| sub open_append : method {
 | |
|     local ($_, %_);
 | |
|     my ($self, $file, $FH, $PH, $CMD);
 | |
|     ($self, $file, $FH) = @_;
 | |
|     # Open the file if it is not opened yet
 | |
|     if (!defined $FH) {
 | |
|         print STDERR "  Opening file in read/write mode ... " if $VERBOSE > 2;
 | |
|         open $FH, "+<", $file           or die "$THIS_FILE: $file: $!";
 | |
|         binmode $FH                     or die "$THIS_FILE: $file: $!";
 | |
|         print STDERR "done\n" if $VERBOSE > 2;
 | |
|     } else {
 | |
|         flock $FH, LOCK_UN;
 | |
|     }
 | |
|     ($self->{"file"}, $self->{"FH"}) = ($file, $FH);
 | |
|     $EXEC = where_is "gzip" if !defined $EXEC;
 | |
| 
 | |
|     # Save the original data if file has content so that file size is
 | |
|     # greater than 0.  STDOUT is always of size 0.
 | |
|     if ((stat $FH)[7] > 0) {
 | |
|         my ($count, $FHT, $PHT, $CMD_T);
 | |
|         seek $FH, 0, SEEK_SET           or die "$THIS_FILE: $file: $!";
 | |
|         # Copy the original content to a buffer
 | |
|         print STDERR "  Reading compressed data to the buffer ... " if $VERBOSE > 2;
 | |
|         $FHT = tempfile                 or die "$THIS_FILE: tempfile: $!";
 | |
|         while (defined($_ = <$FH>)) {
 | |
|             print $FHT $_               or die "$THIS_FILE: tempfile: $!";
 | |
|         }
 | |
|         print STDERR "done\n" if $VERBOSE > 2;
 | |
|         print STDERR "  Restarting file ... " if $VERBOSE > 2;
 | |
|         seek $FHT, 0, SEEK_SET          or die "$THIS_FILE: tempfile: $!";
 | |
|         seek $FH, 0, SEEK_SET           or die "$THIS_FILE: $file: $!";
 | |
|         truncate $FH, 0                 or die "$THIS_FILE: $file: $!";
 | |
|         print STDERR "done\n" if $VERBOSE > 2;
 | |
| 
 | |
|         # Decompress the buffer and save to our file
 | |
|         @_ = ($EXEC, "-cdf");
 | |
|         @_ = map "\"$_\"", @_ if $^O eq "MSWin32";
 | |
|         $CMD_T = join " ", @_;
 | |
|         print STDERR "  Starting $CMD_T from buffer ... " if $VERBOSE > 2;
 | |
|         # Redirect STDIN to $FH
 | |
|         open STDIN, "<&", $FHT          or die "$THIS_FILE: tempfile: $!";
 | |
|         # Start the process
 | |
|         if ($^O eq "MSWin32") {
 | |
|             open $PHT, "$CMD_T |"        or die "$THIS_FILE: $CMD_T: $!";
 | |
|         } else {
 | |
|             open $PHT, "-|", @_         or die "$THIS_FILE: $CMD_T: $!";
 | |
|         }
 | |
|         # Restore STDIN
 | |
|         open STDIN, "<&", $STDIN        or die "$THIS_FILE: STDIN: $!";
 | |
|         print STDERR "done\n" if $VERBOSE > 2;
 | |
| 
 | |
|         @_ = ($EXEC, "-c9f");
 | |
|         @_ = map "\"$_\"", @_ if $^O eq "MSWin32";
 | |
|         $CMD = join " ", @_;
 | |
|         print STDERR "  Starting $CMD to file ... " if $VERBOSE > 2;
 | |
|         # Redirect STDOUT to $FH
 | |
|         open STDOUT, ">&", $FH          or die "$THIS_FILE: $file: $!";
 | |
|         # Start the process
 | |
|         if ($^O eq "MSWin32") {
 | |
|             open $PH, "| $CMD"          or die "$THIS_FILE: $CMD: $!";
 | |
|         } else {
 | |
|             open $PH, "|-", @_          or die "$THIS_FILE: $CMD: $!";
 | |
|         }
 | |
|         # Restore STDOUT
 | |
|         open STDOUT, ">&", $STDOUT      or die "$THIS_FILE: STDOUT: $!";
 | |
|         print STDERR "done\n" if $VERBOSE > 2;
 | |
| 
 | |
|         print STDERR "  Reading old records back from the buffer ... " if $VERBOSE > 2;
 | |
|         $count = 0;
 | |
|         while (defined($_ = <$PHT>)) {
 | |
|             print $PH $_                or die "$THIS_FILE: $file: $!";
 | |
|             $count++;
 | |
|         }
 | |
|         close $PHT                      or die "$THIS_FILE: $CMD_T: $!";
 | |
|         close $FHT                      or die "$THIS_FILE: tempfile: $!";
 | |
|         print STDERR "$count records\n" if $VERBOSE > 2;
 | |
| 
 | |
|     # A whole new file
 | |
|     } else {
 | |
|         @_ = ($EXEC, "-c9f");
 | |
|         @_ = map "\"$_\"", @_ if $^O eq "MSWin32";
 | |
|         $CMD = join " ", @_;
 | |
|         print STDERR "  Starting $CMD to file ... " if $VERBOSE > 2;
 | |
|         # Redirect STDOUT to $FH
 | |
|         open STDOUT, ">&", $FH          or die "$THIS_FILE: $file: $!";
 | |
|         # Start the process
 | |
|         if ($^O eq "MSWin32") {
 | |
|             open $PH, "| $CMD"          or die "$THIS_FILE: $CMD: $!";
 | |
|         } else {
 | |
|             open $PH, "|-", @_          or die "$THIS_FILE: $CMD: $!";
 | |
|         }
 | |
|         # Restore STDOUT
 | |
|         open STDOUT, ">&", $STDOUT      or die "$THIS_FILE: STDOUT: $!";
 | |
|         print STDERR "done\n" if $VERBOSE > 2;
 | |
|     }
 | |
| 
 | |
|     ($self->{"CMD"}, $self->{"PH"}) = ([@_], $PH);
 | |
|     return;
 | |
| }
 | |
| 
 | |
| # Read a line from the I/O stream
 | |
| sub readline : method {
 | |
|     local ($_, %_);
 | |
|     my ($self, $PH);
 | |
|     $self = $_[0];
 | |
|     $PH = $self->{"PH"};
 | |
|     return <$PH>;
 | |
| }
 | |
| 
 | |
| # Output data to the I/O stream
 | |
| sub write : method {
 | |
|     local ($_, %_);
 | |
|     my ($self, $CMD, $PH);
 | |
|     ($self, $_) = @_;
 | |
|     ($CMD, $PH) = (join(" ", @{$self->{"CMD"}}), $self->{"PH"});
 | |
|     print $PH $_                        or die "$THIS_FILE: $CMD: $!";
 | |
|     return;
 | |
| }
 | |
| 
 | |
| # Close the I/O stream
 | |
| sub close : method {
 | |
|     local ($_, %_);
 | |
|     my ($self, $keep, $tmp, $file, $FH, $CMD, $PH);
 | |
|     ($self, $keep, $tmp) = @_;
 | |
|     $keep = KEEP_ALL if @_ < 2;
 | |
|     ($file, $FH) = ($self->{"file"}, $self->{"FH"});
 | |
| 
 | |
|     # Restart the file
 | |
|     if ($keep eq KEEP_RESTART || $keep eq KEEP_THIS_MONTH) {
 | |
|         # Empty the source file
 | |
|         print STDERR "  Emptying file ... " if $VERBOSE > 2;
 | |
|         seek $FH, 0, SEEK_SET           or die "$THIS_FILE: $file: $!";
 | |
|         truncate $FH, 0                 or die "$THIS_FILE: $file: $!";
 | |
|         print STDERR "done\n" if $VERBOSE > 2;
 | |
| 
 | |
|         # Create empty compressed content
 | |
|         print STDERR "  Applying empty compressed content ... " if $VERBOSE > 2;
 | |
|         $EXEC = where_is "gzip" if !defined $EXEC;
 | |
|         @_ = ($EXEC, "-c9f");
 | |
|         @_ = map "\"$_\"", @_ if $^O eq "MSWin32";
 | |
|         $CMD = join " ", @_;
 | |
|         # Redirect STDOUT to $FH
 | |
|         open STDOUT, ">&", $FH          or die "$THIS_FILE: $file: $!";
 | |
|         # Start the process and end it
 | |
|         if ($^O eq "MSWin32") {
 | |
|             open $PH, "| $CMD"          or die "$THIS_FILE: $CMD: $!";
 | |
|         } else {
 | |
|             open $PH, "|-", @_          or die "$THIS_FILE: $CMD: $!";
 | |
|         }
 | |
|         close $PH                       or die "$THIS_FILE: $CMD: $!";
 | |
|         # Restore STDOUT
 | |
|         open STDOUT, ">&", $STDOUT      or die "$THIS_FILE: STDOUT: $!";
 | |
|         print STDERR "done\n" if $VERBOSE > 2;
 | |
|     }
 | |
| 
 | |
|     ($CMD, $PH) = (join(" ", @{$self->{"CMD"}}), $self->{"PH"});
 | |
|     CORE::close $PH                     or die "$THIS_FILE: $CMD: $!";
 | |
|     CORE::close $FH                     or die "$THIS_FILE: $file: $!";
 | |
|     delete $self->{"PH"};
 | |
|     delete $self->{"CMD"};
 | |
|     delete $self->{"FH"};
 | |
|     delete $self->{"file"};
 | |
| 
 | |
|     # Delete the file
 | |
|     if ($keep eq KEEP_DELETE) {
 | |
|         print STDERR "  Deleting file ... " if $VERBOSE > 2;
 | |
|         unlink $file                    or die "$THIS_FILE: $file: $!";
 | |
|         print STDERR "done\n" if $VERBOSE > 2;
 | |
|     }
 | |
|     # Delete the temporary file if needed
 | |
|     if (defined $tmp && -e $tmp) {
 | |
|         unlink $tmp                     or die "$THIS_FILE: $tmp: $!";
 | |
|     }
 | |
|     return;
 | |
| }
 | |
| 
 | |
| 
 | |
| # _private::IO::Bzip2::PM: The bzip2 module compression I/O handler
 | |
| package _private::IO::Bzip2::PM;
 | |
| use 5.008;
 | |
| use strict;
 | |
| use warnings;
 | |
| use base qw(_private::IO);
 | |
| BEGIN {
 | |
| import main;
 | |
| }
 | |
| 
 | |
| use Fcntl qw(:flock :seek);
 | |
| use File::Temp qw(tempfile);
 | |
| 
 | |
| # The file name suffix of this mime type
 | |
| sub suffix : method { ".bz2"; }
 | |
| 
 | |
| # Open the file for reading
 | |
| sub open_read : method {
 | |
|     local ($_, %_);
 | |
|     my ($self, $file, $FH);
 | |
|     ($self, $file, $FH) = @_;
 | |
|     # Open the file if it is not opened yet
 | |
|     if (!defined $FH) {
 | |
|         print STDERR "  Opening file in read mode ... " if $VERBOSE > 2;
 | |
|         open $FH, "+<", $file           or die "$THIS_FILE: $file: $!";
 | |
|         binmode $FH                     or die "$THIS_FILE: $file: $!";
 | |
|         flock $FH, LOCK_EX;
 | |
|         print STDERR "done\n" if $VERBOSE > 2;
 | |
|     }
 | |
|     ($self->{"file"}, $self->{"FH"}) = ($file, $FH);
 | |
|     import Compress::Bzip2 qw(bzopen);
 | |
|     print STDERR "  Attaching file with bzopen(..., \"rb\") ... " if $VERBOSE > 2;
 | |
|     $self->{"bz"} = bzopen($FH, "rb")   or die "$THIS_FILE: $file: $!";
 | |
|     print STDERR "done\n" if $VERBOSE > 2;
 | |
|     return;
 | |
| }
 | |
| 
 | |
| # Open the file for writing
 | |
| sub open_write : method {
 | |
|     local ($_, %_);
 | |
|     my ($self, $file, $FH);
 | |
|     ($self, $file, $FH) = @_;
 | |
|     # Open the file if it is not opened yet
 | |
|     if (!defined $FH) {
 | |
|         print STDERR "  Creating file in write mode ... " if $VERBOSE > 2;
 | |
|         open $FH, "+>", $file           or die "$THIS_FILE: $file: $!";
 | |
|         binmode $FH                     or die "$THIS_FILE: $file: $!";
 | |
|         flock $FH, LOCK_EX;
 | |
|         print STDERR "done\n" if $VERBOSE > 2;
 | |
|     }
 | |
|     ($self->{"file"}, $self->{"FH"}) = ($file, $FH);
 | |
|     import Compress::Bzip2 qw(bzopen);
 | |
|     print STDERR "  Attaching file with bzopen(..., \"wb9\") ... " if $VERBOSE > 2;
 | |
|     $self->{"bz"} = bzopen($FH, "wb9")  or die "$THIS_FILE: $file: $!";
 | |
|     print STDERR "done\n" if $VERBOSE > 2;
 | |
|     return;
 | |
| }
 | |
| 
 | |
| # Open the file for appending
 | |
| sub open_append : method {
 | |
|     local ($_, %_);
 | |
|     my ($self, $file, $FH, $bz);
 | |
|     ($self, $file, $FH) = @_;
 | |
|     # Open the file if it is not opened yet
 | |
|     if (!defined $FH) {
 | |
|         print STDERR "  Opening file in read/write mode ... " if $VERBOSE > 2;
 | |
|         open $FH, "+<", $file           or die "$THIS_FILE: $file: $!";
 | |
|         binmode $FH                     or die "$THIS_FILE: $file: $!";
 | |
|         flock $FH, LOCK_EX;
 | |
|         print STDERR "done\n" if $VERBOSE > 2;
 | |
|     }
 | |
|     ($self->{"file"}, $self->{"FH"}) = ($file, $FH);
 | |
|     import Compress::Bzip2 qw(bzopen);
 | |
| 
 | |
|     # Save the original data if file has content so that file size is
 | |
|     # greater than 0.  STDOUT is always of size 0.
 | |
|     if ((stat $FH)[7] > 0) {
 | |
|         my ($count, $FHT, $bzt, $n);
 | |
|         seek $FH, 0, SEEK_SET           or die "$THIS_FILE: $file: $!";
 | |
|         # Copy the original content to a buffer
 | |
|         print STDERR "  Reading compressed data to the buffer ... " if $VERBOSE > 2;
 | |
|         $FHT = tempfile                 or die "$THIS_FILE: tempfile: $!";
 | |
|         while (defined($_ = <$FH>)) {
 | |
|             print $FHT $_               or die "$THIS_FILE: tempfile: $!";
 | |
|         }
 | |
|         print STDERR "done\n" if $VERBOSE > 2;
 | |
|         print STDERR "  Restarting file ... " if $VERBOSE > 2;
 | |
|         seek $FHT, 0, SEEK_SET          or die "$THIS_FILE: tempfile: $!";
 | |
|         seek $FH, 0, SEEK_SET           or die "$THIS_FILE: $file: $!";
 | |
|         truncate $FH, 0                 or die "$THIS_FILE: $file: $!";
 | |
|         print STDERR "done\n" if $VERBOSE > 2;
 | |
| 
 | |
|         # Decompress the buffer and save to our file
 | |
|         print STDERR "  Attaching buffer with bzopen(..., \"rb\") ... " if $VERBOSE > 2;
 | |
|         $bzt = bzopen($FHT, "rb")       or die "$THIS_FILE: tempfile: $!";
 | |
|         print STDERR "done\n" if $VERBOSE > 2;
 | |
|         print STDERR "  Attaching file with bzopen(..., \"wb9\") ... " if $VERBOSE > 2;
 | |
|         $bz = bzopen($FH, "wb9")        or die "$THIS_FILE: $file: $!";
 | |
|         print STDERR "done\n" if $VERBOSE > 2;
 | |
| 
 | |
|         print STDERR "  Reading old records back from the buffer ... " if $VERBOSE > 2;
 | |
|         $count = 0;
 | |
|         while (($n = $bzt->bzreadline($_)) != 0) {
 | |
|             die "$THIS_FILE: tempfile: " . $bz->bzerror if $n == -1;
 | |
|             ($bz->bzwrite($_, length $_) == length $_)
 | |
|                                         or die "$THIS_FILE: $file: " . $bz->bzerror;
 | |
|             $count++;
 | |
|         }
 | |
|         close $FHT                      or die "$THIS_FILE: tempfile: $!";
 | |
|         print STDERR "$count records\n" if $VERBOSE > 2;
 | |
| 
 | |
|     # A whole new file
 | |
|     } else {
 | |
|         print STDERR "  Attaching file with bzopen(..., \"wb9\") ... " if $VERBOSE > 2;
 | |
|         $bz = bzopen($FH, "wb9")        or die "$THIS_FILE: $file: $!";
 | |
|         print STDERR "done\n" if $VERBOSE > 2;
 | |
|     }
 | |
| 
 | |
|     $self->{"bz"} = $bz;
 | |
|     return;
 | |
| }
 | |
| 
 | |
| # Read a line from the I/O stream
 | |
| sub readline : method {
 | |
|     local ($_, %_);
 | |
|     my ($self, $file, $bz, $n);
 | |
|     $self = $_[0];
 | |
|     ($file, $bz) = ($self->{"file"}, $self->{"bz"});
 | |
|     (($n = $bz->bzreadline($_)) != -1)  or die "$THIS_FILE: $file: " . $bz->bzerror;
 | |
|     return undef if $n == 0;
 | |
|     return $_;
 | |
| }
 | |
| 
 | |
| # Output data to the I/O stream
 | |
| sub write : method {
 | |
|     local ($_, %_);
 | |
|     my ($self, $file, $bz);
 | |
|     ($self, $_) = @_;
 | |
|     ($file, $bz) = ($self->{"file"}, $self->{"bz"});
 | |
|     ($bz->bzwrite($_, length $_) == length $_)
 | |
|                                         or die "$THIS_FILE: $file: " . $bz->bzerror;
 | |
|     return;
 | |
| }
 | |
| 
 | |
| # Close the I/O stream
 | |
| sub close : method {
 | |
|     local ($_, %_);
 | |
|     my ($self, $keep, $tmp, $file, $FH, $bz);
 | |
|     ($self, $keep, $tmp) = @_;
 | |
|     $keep = KEEP_ALL if @_ < 2;
 | |
|     ($file, $FH, $bz) = ($self->{"file"}, $self->{"FH"}, $self->{"bz"});
 | |
| 
 | |
|     # Restart the file
 | |
|     if ($keep eq KEEP_RESTART || $keep eq KEEP_THIS_MONTH) {
 | |
|         # Empty the source file
 | |
|         print STDERR "  Emptying file ... " if $VERBOSE > 2;
 | |
|         seek $FH, 0, SEEK_SET           or die "$THIS_FILE: $file: $!";
 | |
|         truncate $FH, 0                 or die "$THIS_FILE: $file: $!";
 | |
|         print STDERR "done\n" if $VERBOSE > 2;
 | |
| 
 | |
|         # Create empty compressed content
 | |
|         print STDERR "  Applying empty compressed content ... " if $VERBOSE > 2;
 | |
|         $_ = bzopen($FH, "wb9")         or die "$THIS_FILE: $file: $!";
 | |
|         $_->bzclose                     and die "$THIS_FILE: $file: " . $_->bzerror;
 | |
|         undef $_;
 | |
|         undef $bz;
 | |
|         print STDERR "done\n" if $VERBOSE > 2;
 | |
|     }
 | |
| 
 | |
|     if (defined $bz) {
 | |
|         $bz->bzclose                    and die "$THIS_FILE: $file: " . $bz->bzerror;
 | |
|     }
 | |
|     CORE::close $self->{"FH"} if $self->{"FH"}->opened;
 | |
|     delete $self->{"bz"};
 | |
|     delete $self->{"FH"};
 | |
|     delete $self->{"file"};
 | |
| 
 | |
|     # Delete the file
 | |
|     if ($keep eq KEEP_DELETE) {
 | |
|         print STDERR "  Deleting file ... " if $VERBOSE > 2;
 | |
|         unlink $file                    or die "$THIS_FILE: $file: $!";
 | |
|         print STDERR "done\n" if $VERBOSE > 2;
 | |
|     }
 | |
|     # Delete the temporary file if needed
 | |
|     if (defined $tmp && -e $tmp) {
 | |
|         unlink $tmp                     or die "$THIS_FILE: $tmp: $!";
 | |
|     }
 | |
|     return;
 | |
| }
 | |
| 
 | |
| 
 | |
| # _private::IO::Bzip2::Exec: The bzip2 executable compression I/O handler
 | |
| package _private::IO::Bzip2::Exec;
 | |
| use 5.008;
 | |
| use strict;
 | |
| use warnings;
 | |
| use base qw(_private::IO);
 | |
| BEGIN {
 | |
| import main;
 | |
| }
 | |
| 
 | |
| use Fcntl qw(:flock :seek);
 | |
| use File::Temp qw(tempfile);
 | |
| 
 | |
| our ($EXEC);
 | |
| BEGIN {
 | |
| undef $EXEC;
 | |
| }
 | |
| 
 | |
| # The file name suffix of this mime type
 | |
| sub suffix : method { ".bz2"; }
 | |
| 
 | |
| # Open the file for reading
 | |
| sub open_read : method {
 | |
|     local ($_, %_);
 | |
|     my ($self, $file, $FH, $PH, $CMD);
 | |
|     ($self, $file, $FH) = @_;
 | |
|     # Open the file if it is not opened yet
 | |
|     if (!defined $FH) {
 | |
|         print STDERR "  Opening file in read mode ... " if $VERBOSE > 2;
 | |
|         open $FH, "+<", $file           or die "$THIS_FILE: $file: $!";
 | |
|         binmode $FH                     or die "$THIS_FILE: $file: $!";
 | |
|         print STDERR "done\n" if $VERBOSE > 2;
 | |
|     } else {
 | |
|         flock $FH, LOCK_UN;
 | |
|     }
 | |
|     ($self->{"file"}, $self->{"FH"}) = ($file, $FH);
 | |
|     $EXEC = where_is "bzip2" if !defined $EXEC;
 | |
| 
 | |
|     @_ = ($EXEC, "-cdf");
 | |
|     @_ = map "\"$_\"", @_ if $^O eq "MSWin32";
 | |
|     $CMD = join " ", @_;
 | |
|     print STDERR "  Starting $CMD from file ... " if $VERBOSE > 2;
 | |
|     # Redirect STDIN to $FH
 | |
|     open STDIN, "<&", $FH               or die "$THIS_FILE: $file: $!";
 | |
|     # Start the process
 | |
|     if ($^O eq "MSWin32") {
 | |
|         open $PH, "$CMD |"              or die "$THIS_FILE: $CMD: $!";
 | |
|     } else {
 | |
|         open $PH, "-|", @_              or die "$THIS_FILE: $CMD: $!";
 | |
|     }
 | |
|     # Restore STDIN
 | |
|     open STDIN, "<&", $STDIN            or die "$THIS_FILE: STDIN: $!";
 | |
|     print STDERR "done\n" if $VERBOSE > 2;
 | |
|     ($self->{"CMD"}, $self->{"PH"}) = ([@_], $PH);
 | |
|     return;
 | |
| }
 | |
| 
 | |
| # Open the file for writing
 | |
| sub open_write : method {
 | |
|     local ($_, %_);
 | |
|     my ($self, $file, $FH, $PH, $CMD);
 | |
|     ($self, $file, $FH) = @_;
 | |
|     # Open the file if it is not opened yet
 | |
|     if (!defined $FH) {
 | |
|         print STDERR "  Creating file in write mode ... " if $VERBOSE > 2;
 | |
|         open $FH, "+>", $file           or die "$THIS_FILE: $file: $!";
 | |
|         binmode $FH                     or die "$THIS_FILE: $file: $!";
 | |
|         print STDERR "done\n" if $VERBOSE > 2;
 | |
|     } else {
 | |
|         flock $FH, LOCK_UN;
 | |
|     }
 | |
|     ($self->{"file"}, $self->{"FH"}) = ($file, $FH);
 | |
|     $EXEC = where_is "bzip2" if !defined $EXEC;
 | |
| 
 | |
|     @_ = ($EXEC, "-9f");
 | |
|     @_ = map "\"$_\"", @_ if $^O eq "MSWin32";
 | |
|     $CMD = join " ", @_;
 | |
|     print STDERR "  Starting $CMD to file ... " if $VERBOSE > 2;
 | |
|     # Redirect STDOUT to $FH
 | |
|     open STDOUT, ">&", $FH              or die "$THIS_FILE: $file: $!";
 | |
|     # Start the process
 | |
|     if ($^O eq "MSWin32") {
 | |
|         open $PH, "| $CMD"              or die "$THIS_FILE: $CMD: $!";
 | |
|     } else {
 | |
|         open $PH, "|-", @_              or die "$THIS_FILE: $CMD: $!";
 | |
|     }
 | |
|     # Restore STDOUT
 | |
|     open STDOUT, ">&", $STDOUT          or die "$THIS_FILE: STDOUT: $!";
 | |
|     print STDERR "done\n" if $VERBOSE > 2;
 | |
|     ($self->{"CMD"}, $self->{"PH"}) = ([@_], $PH);
 | |
|     return;
 | |
| }
 | |
| 
 | |
| # Open the file for appending
 | |
| sub open_append : method {
 | |
|     local ($_, %_);
 | |
|     my ($self, $file, $FH, $PH, $CMD);
 | |
|     ($self, $file, $FH) = @_;
 | |
|     # Open the file if it is not opened yet
 | |
|     if (!defined $FH) {
 | |
|         print STDERR "  Opening file in read/write mode ... " if $VERBOSE > 2;
 | |
|         open $FH, "+<", $file           or die "$THIS_FILE: $file: $!";
 | |
|         binmode $FH                     or die "$THIS_FILE: $file: $!";
 | |
|         print STDERR "done\n" if $VERBOSE > 2;
 | |
|     } else {
 | |
|         flock $FH, LOCK_UN;
 | |
|     }
 | |
|     ($self->{"file"}, $self->{"FH"}) = ($file, $FH);
 | |
|     $EXEC = where_is "bzip2" if !defined $EXEC;
 | |
| 
 | |
|     # Save the original data if file has content so that file size is
 | |
|     # greater than 0.  STDOUT is always of size 0.
 | |
|     if ((stat $FH)[7] > 0) {
 | |
|         my ($count, $FHT, $PHT, $CMD_T);
 | |
|         seek $FH, 0, SEEK_SET           or die "$THIS_FILE: $file: $!";
 | |
|         # Copy the original content to a buffer
 | |
|         print STDERR "  Reading compressed data to the buffer ... " if $VERBOSE > 2;
 | |
|         $FHT = tempfile                 or die "$THIS_FILE: tempfile: $!";
 | |
|         while (defined($_ = <$FH>)) {
 | |
|             print $FHT $_               or die "$THIS_FILE: tempfile: $!";
 | |
|         }
 | |
|         print STDERR "done\n" if $VERBOSE > 2;
 | |
|         print STDERR "  Restarting file ... " if $VERBOSE > 2;
 | |
|         seek $FHT, 0, SEEK_SET          or die "$THIS_FILE: tempfile: $!";
 | |
|         seek $FH, 0, SEEK_SET           or die "$THIS_FILE: $file: $!";
 | |
|         truncate $FH, 0                 or die "$THIS_FILE: $file: $!";
 | |
|         print STDERR "done\n" if $VERBOSE > 2;
 | |
| 
 | |
|         # Decompress the buffer and save to our file
 | |
|         @_ = ($EXEC, "-cdf");
 | |
|         @_ = map "\"$_\"", @_ if $^O eq "MSWin32";
 | |
|         $CMD_T = join " ", @_;
 | |
|         print STDERR "  Starting $CMD_T from buffer ... " if $VERBOSE > 2;
 | |
|         # Redirect STDIN to $FH
 | |
|         open STDIN, "<&", $FHT          or die "$THIS_FILE: tempfile: $!";
 | |
|         # Start the process
 | |
|         if ($^O eq "MSWin32") {
 | |
|             open $PHT, "$CMD_T |"        or die "$THIS_FILE: $CMD_T: $!";
 | |
|         } else {
 | |
|             open $PHT, "-|", @_         or die "$THIS_FILE: $CMD_T: $!";
 | |
|         }
 | |
|         # Restore STDIN
 | |
|         open STDIN, "<&", $STDIN        or die "$THIS_FILE: STDIN: $!";
 | |
|         print STDERR "done\n" if $VERBOSE > 2;
 | |
| 
 | |
|         @_ = ($EXEC, "-9f");
 | |
|         @_ = map "\"$_\"", @_ if $^O eq "MSWin32";
 | |
|         $CMD = join " ", @_;
 | |
|         print STDERR "  Starting $CMD to file ... " if $VERBOSE > 2;
 | |
|         # Redirect STDOUT to $FH
 | |
|         open STDOUT, ">&", $FH          or die "$THIS_FILE: $file: $!";
 | |
|         # Start the process
 | |
|         if ($^O eq "MSWin32") {
 | |
|             open $PH, "| $CMD"          or die "$THIS_FILE: $CMD: $!";
 | |
|         } else {
 | |
|             open $PH, "|-", @_          or die "$THIS_FILE: $CMD: $!";
 | |
|         }
 | |
|         # Restore STDOUT
 | |
|         open STDOUT, ">&", $STDOUT      or die "$THIS_FILE: STDOUT: $!";
 | |
|         print STDERR "done\n" if $VERBOSE > 2;
 | |
| 
 | |
|         print STDERR "  Reading old records back from the buffer ... " if $VERBOSE > 2;
 | |
|         $count = 0;
 | |
|         while (defined($_ = <$PHT>)) {
 | |
|             print $PH $_                or die "$THIS_FILE: $file: $!";
 | |
|             $count++;
 | |
|         }
 | |
|         close $PHT                      or die "$THIS_FILE: $CMD_T: $!";
 | |
|         close $FHT                      or die "$THIS_FILE: tempfile: $!";
 | |
|         print STDERR "$count records\n" if $VERBOSE > 2;
 | |
| 
 | |
|     # A whole new file
 | |
|     } else {
 | |
|         @_ = ($EXEC, "-9f");
 | |
|         @_ = map "\"$_\"", @_ if $^O eq "MSWin32";
 | |
|         $CMD = join " ", @_;
 | |
|         print STDERR "  Starting $CMD to file ... " if $VERBOSE > 2;
 | |
|         # Redirect STDOUT to $FH
 | |
|         open STDOUT, ">&", $FH          or die "$THIS_FILE: $file: $!";
 | |
|         # Start the process
 | |
|         if ($^O eq "MSWin32") {
 | |
|             open $PH, "| $CMD"          or die "$THIS_FILE: $CMD: $!";
 | |
|         } else {
 | |
|             open $PH, "|-", @_          or die "$THIS_FILE: $CMD: $!";
 | |
|         }
 | |
|         # Restore STDOUT
 | |
|         open STDOUT, ">&", $STDOUT      or die "$THIS_FILE: STDOUT: $!";
 | |
|         print STDERR "done\n" if $VERBOSE > 2;
 | |
|     }
 | |
| 
 | |
|     ($self->{"CMD"}, $self->{"PH"}) = ([@_], $PH);
 | |
|     return;
 | |
| }
 | |
| 
 | |
| # Read a line from the I/O stream
 | |
| sub readline : method {
 | |
|     local ($_, %_);
 | |
|     my ($self, $PH);
 | |
|     $self = $_[0];
 | |
|     $PH = $self->{"PH"};
 | |
|     return <$PH>;
 | |
| }
 | |
| 
 | |
| # Output data to the I/O stream
 | |
| sub write : method {
 | |
|     local ($_, %_);
 | |
|     my ($self, $CMD, $PH);
 | |
|     ($self, $_) = @_;
 | |
|     ($CMD, $PH) = (join(" ", @{$self->{"CMD"}}), $self->{"PH"});
 | |
|     print $PH $_                        or die "$THIS_FILE: $CMD: $!";
 | |
|     return;
 | |
| }
 | |
| 
 | |
| # Close the I/O stream
 | |
| sub close : method {
 | |
|     local ($_, %_);
 | |
|     my ($self, $keep, $tmp, $file, $FH, $CMD, $PH);
 | |
|     ($self, $keep, $tmp) = @_;
 | |
|     $keep = KEEP_ALL if @_ < 2;
 | |
|     ($file, $FH) = ($self->{"file"}, $self->{"FH"});
 | |
| 
 | |
|     # Restart the file
 | |
|     if ($keep eq KEEP_RESTART || $keep eq KEEP_THIS_MONTH) {
 | |
|         my ($CMD, $PH);
 | |
|         # Empty the source file
 | |
|         print STDERR "  Emptying file ... " if $VERBOSE > 2;
 | |
|         seek $FH, 0, SEEK_SET           or die "$THIS_FILE: $file: $!";
 | |
|         truncate $FH, 0                 or die "$THIS_FILE: $file: $!";
 | |
|         print STDERR "done\n" if $VERBOSE > 2;
 | |
| 
 | |
|         # Create empty compressed content
 | |
|         print STDERR "  Applying empty compressed content ... " if $VERBOSE > 2;
 | |
|         $EXEC = where_is "bzip2" if !defined $EXEC;
 | |
|         @_ = ($EXEC, "-9f");
 | |
|         @_ = map "\"$_\"", @_ if $^O eq "MSWin32";
 | |
|         $CMD = join " ", @_;
 | |
|         # Redirect STDOUT to $FH
 | |
|         open STDOUT, ">&", $FH          or die "$THIS_FILE: $file: $!";
 | |
|         # Start the process and end it
 | |
|         if ($^O eq "MSWin32") {
 | |
|             open $PH, "| $CMD"          or die "$THIS_FILE: $CMD: $!";
 | |
|         } else {
 | |
|             open $PH, "|-", @_          or die "$THIS_FILE: $CMD: $!";
 | |
|         }
 | |
|         close $PH                       or die "$THIS_FILE: $CMD: $!";
 | |
|         # Restore STDOUT
 | |
|         open STDOUT, ">&", $STDOUT      or die "$THIS_FILE: STDOUT: $!";
 | |
|         print STDERR "done\n" if $VERBOSE > 2;
 | |
|     }
 | |
| 
 | |
|     ($CMD, $PH) = (join(" ", @{$self->{"CMD"}}), $self->{"PH"});
 | |
|     CORE::close $PH                     or die "$THIS_FILE: $CMD: $!";
 | |
|     CORE::close $FH                     or die "$THIS_FILE: $file: $!";
 | |
|     delete $self->{"PH"};
 | |
|     delete $self->{"CMD"};
 | |
|     delete $self->{"FH"};
 | |
|     delete $self->{"file"};
 | |
| 
 | |
|     # Delete the file
 | |
|     if ($keep eq KEEP_DELETE) {
 | |
|         print STDERR "  Deleting file ... " if $VERBOSE > 2;
 | |
|         unlink $file                    or die "$THIS_FILE: $file: $!";
 | |
|         print STDERR "done\n" if $VERBOSE > 2;
 | |
|     }
 | |
|     # Delete the temporary file if needed
 | |
|     if (defined $tmp && -e $tmp) {
 | |
|         unlink $tmp                     or die "$THIS_FILE: $tmp: $!";
 | |
|     }
 | |
|     return;
 | |
| }
 | |
| 
 | |
| 
 | |
| # _private::IO::Xz::PM: The xz module compression I/O handler
 | |
| package _private::IO::Xz::PM;
 | |
| use 5.008;
 | |
| use strict;
 | |
| use warnings;
 | |
| use base qw(_private::IO);
 | |
| BEGIN {
 | |
| import main;
 | |
| }
 | |
| 
 | |
| use Fcntl qw(:flock :seek);
 | |
| use File::Temp qw(tempfile);
 | |
| 
 | |
| # The file name suffix of this mime type
 | |
| sub suffix : method { ".xz"; }
 | |
| 
 | |
| # Open the file for reading
 | |
| sub open_read : method {
 | |
|     local ($_, %_);
 | |
|     my ($self, $file, $FH);
 | |
|     ($self, $file, $FH) = @_;
 | |
|     # Open the file if it is not opened yet
 | |
|     if (!defined $FH) {
 | |
|         print STDERR "  Opening file in read mode ... " if $VERBOSE > 2;
 | |
|         open $FH, "+<", $file           or die "$THIS_FILE: $file: $!";
 | |
|         binmode $FH                     or die "$THIS_FILE: $file: $!";
 | |
|         flock $FH, LOCK_EX;
 | |
|         print STDERR "done\n" if $VERBOSE > 2;
 | |
|     }
 | |
|     ($self->{"file"}, $self->{"FH"}) = ($file, $FH);
 | |
|     print STDERR "  Attaching file with IO::Uncompress::UnXz ... " if $VERBOSE > 2;
 | |
|     $self->{"xz"} = IO::Uncompress::UnXz->new($FH)
 | |
|                                         or die "$THIS_FILE: $file: $IO::Uncompress::UnXz::UnXzError";
 | |
|     print STDERR "done\n" if $VERBOSE > 2;
 | |
|     return;
 | |
| }
 | |
| 
 | |
| # Open the file for writing
 | |
| sub open_write : method {
 | |
|     local ($_, %_);
 | |
|     my ($self, $file, $FH);
 | |
|     ($self, $file, $FH) = @_;
 | |
|     # Open the file if it is not opened yet
 | |
|     if (!defined $FH) {
 | |
|         print STDERR "  Creating file in write mode ... " if $VERBOSE > 2;
 | |
|         open $FH, "+>", $file           or die "$THIS_FILE: $file: $!";
 | |
|         binmode $FH                     or die "$THIS_FILE: $file: $!";
 | |
|         flock $FH, LOCK_EX;
 | |
|         print STDERR "done\n" if $VERBOSE > 2;
 | |
|     }
 | |
|     ($self->{"file"}, $self->{"FH"}) = ($file, $FH);
 | |
|     print STDERR "  Attaching file with IO::Compress::Xz ... " if $VERBOSE > 2;
 | |
|     $self->{"xz"} = IO::Compress::Xz->new($FH)
 | |
|                                         or die "$THIS_FILE: $file: $IO::Compress::Xz::XzError";
 | |
|     print STDERR "done\n" if $VERBOSE > 2;
 | |
|     return;
 | |
| }
 | |
| 
 | |
| # Open the file for appending
 | |
| sub open_append : method {
 | |
|     local ($_, %_);
 | |
|     my ($self, $file, $FH, $xz);
 | |
|     ($self, $file, $FH) = @_;
 | |
|     # Open the file if it is not opened yet
 | |
|     if (!defined $FH) {
 | |
|         print STDERR "  Opening file in read/write mode ... " if $VERBOSE > 2;
 | |
|         open $FH, "+<", $file           or die "$THIS_FILE: $file: $!";
 | |
|         binmode $FH                     or die "$THIS_FILE: $file: $!";
 | |
|         flock $FH, LOCK_EX;
 | |
|         print STDERR "done\n" if $VERBOSE > 2;
 | |
|     }
 | |
|     ($self->{"file"}, $self->{"FH"}) = ($file, $FH);
 | |
| 
 | |
|     # Save the original data if file has content so that file size is
 | |
|     # greater than 0.  STDOUT is always of size 0.
 | |
|     if ((stat $FH)[7] > 0) {
 | |
|         my ($count, $FHT, $xzt, $n);
 | |
|         seek $FH, 0, SEEK_SET           or die "$THIS_FILE: $file: $!";
 | |
|         # Copy the original content to a buffer
 | |
|         print STDERR "  Reading compressed data to the buffer ... " if $VERBOSE > 2;
 | |
|         $FHT = tempfile                 or die "$THIS_FILE: tempfile: $!";
 | |
|         while (defined($_ = <$FH>)) {
 | |
|             print $FHT $_               or die "$THIS_FILE: tempfile: $!";
 | |
|         }
 | |
|         print STDERR "done\n" if $VERBOSE > 2;
 | |
|         print STDERR "  Restarting file ... " if $VERBOSE > 2;
 | |
|         seek $FHT, 0, SEEK_SET          or die "$THIS_FILE: tempfile: $!";
 | |
|         seek $FH, 0, SEEK_SET           or die "$THIS_FILE: $file: $!";
 | |
|         truncate $FH, 0                 or die "$THIS_FILE: $file: $!";
 | |
|         print STDERR "done\n" if $VERBOSE > 2;
 | |
| 
 | |
|         # Decompress the buffer and save to our file
 | |
|         print STDERR "  Attaching buffer with IO::Uncompress::UnXz ... " if $VERBOSE > 2;
 | |
|         $xzt = IO::Uncompress::UnXz->new($FHT)
 | |
|                                         or die "$THIS_FILE: tempfile: $IO::Uncompress::UnXz::UnXzError";
 | |
|         print STDERR "done\n" if $VERBOSE > 2;
 | |
|         print STDERR "  Attaching file with IO::Compress::Xz ... " if $VERBOSE > 2;
 | |
|         $xz = IO::Compress::Xz->new($FH)
 | |
|                                         or die "$THIS_FILE: $file: $IO::Compress::Xz::XzError";
 | |
|         print STDERR "done\n" if $VERBOSE > 2;
 | |
| 
 | |
|         print STDERR "  Reading old records back from the buffer ... " if $VERBOSE > 2;
 | |
|         $count = 0;
 | |
|         while (defined($_ = $xzt->getline)) {
 | |
|             ($xz->write($_) == length $_)
 | |
|                                         or die "$THIS_FILE: $file: $IO::Compress::Xz::XzError";
 | |
|             $count++;
 | |
|         }
 | |
|         close $FHT                      or die "$THIS_FILE: tempfile: $!";
 | |
|         print STDERR "$count records\n" if $VERBOSE > 2;
 | |
| 
 | |
|     # A whole new file
 | |
|     } else {
 | |
|         print STDERR "  Attaching file with IO::Compress::Xz ... " if $VERBOSE > 2;
 | |
|         $xz = IO::Compress::Xz->new($FH)
 | |
|                                         or die "$THIS_FILE: $file: $IO::Compress::Xz::XzError";
 | |
|         print STDERR "done\n" if $VERBOSE > 2;
 | |
|     }
 | |
| 
 | |
|     $self->{"xz"} = $xz;
 | |
|     return;
 | |
| }
 | |
| 
 | |
| # Read a line from the I/O stream
 | |
| sub readline : method {
 | |
|     return $_[0]->{"xz"}->getline();
 | |
| }
 | |
| 
 | |
| # Output data to the I/O stream
 | |
| sub write : method {
 | |
|     local ($_, %_);
 | |
|     my ($self, $file, $xz);
 | |
|     ($self, $_) = @_;
 | |
|     ($file, $xz) = ($self->{"file"}, $self->{"xz"});
 | |
|     ($xz->write($_) == length $_)       or die "$THIS_FILE: $file: $IO::Compress::Xz::XzError";
 | |
|     return;
 | |
| }
 | |
| 
 | |
| # Close the I/O stream
 | |
| sub close : method {
 | |
|     local ($_, %_);
 | |
|     my ($self, $keep, $tmp, $file, $FH, $xz);
 | |
|     ($self, $keep, $tmp) = @_;
 | |
|     $keep = KEEP_ALL if @_ < 2;
 | |
|     ($file, $FH, $xz) = ($self->{"file"}, $self->{"FH"}, $self->{"xz"});
 | |
| 
 | |
|     # Restart the file
 | |
|     if ($keep eq KEEP_RESTART || $keep eq KEEP_THIS_MONTH) {
 | |
|         # Empty the source file
 | |
|         print STDERR "  Emptying file ... " if $VERBOSE > 2;
 | |
|         seek $FH, 0, SEEK_SET           or die "$THIS_FILE: $file: $!";
 | |
|         truncate $FH, 0                 or die "$THIS_FILE: $file: $!";
 | |
|         print STDERR "done\n" if $VERBOSE > 2;
 | |
| 
 | |
|         # Create empty compressed content
 | |
|         print STDERR "  Applying empty compressed content ... " if $VERBOSE > 2;
 | |
|         $_ = IO::Compress::Xz->new($FH, Append => 0)
 | |
|                                         or die "$THIS_FILE: $file: $IO::Compress::Xz::XzError";
 | |
|         $_->close                       or die "$THIS_FILE: $file: $IO::Compress::Xz::XzError";
 | |
|         undef $_;
 | |
|         undef $xz;
 | |
|         print STDERR "done\n" if $VERBOSE > 2;
 | |
|     }
 | |
| 
 | |
|     if (defined $xz) {
 | |
|         $xz->close                      or die "$THIS_FILE: $file: $IO::Compress::Xz::XzError!";
 | |
|     }
 | |
|     CORE::close $self->{"FH"} if $self->{"FH"}->opened;
 | |
|     delete $self->{"xz"};
 | |
|     delete $self->{"FH"};
 | |
|     delete $self->{"file"};
 | |
| 
 | |
|     # Delete the file
 | |
|     if ($keep eq KEEP_DELETE) {
 | |
|         print STDERR "  Deleting file ... " if $VERBOSE > 2;
 | |
|         unlink $file                    or die "$THIS_FILE: $file: $!";
 | |
|         print STDERR "done\n" if $VERBOSE > 2;
 | |
|     }
 | |
|     # Delete the temporary file if needed
 | |
|     if (defined $tmp && -e $tmp) {
 | |
|         unlink $tmp                     or die "$THIS_FILE: $tmp: $!";
 | |
|     }
 | |
|     return;
 | |
| }
 | |
| 
 | |
| 
 | |
| # _private::IO::Xz::Exec: The xz executable compression I/O handler
 | |
| package _private::IO::Xz::Exec;
 | |
| use 5.008;
 | |
| use strict;
 | |
| use warnings;
 | |
| use base qw(_private::IO);
 | |
| BEGIN {
 | |
| import main;
 | |
| }
 | |
| 
 | |
| use Fcntl qw(:flock :seek);
 | |
| use File::Temp qw(tempfile);
 | |
| 
 | |
| our ($EXEC);
 | |
| BEGIN {
 | |
| undef $EXEC;
 | |
| }
 | |
| 
 | |
| # The file name suffix of this mime type
 | |
| sub suffix : method { ".xz"; }
 | |
| 
 | |
| # Open the file for reading
 | |
| sub open_read : method {
 | |
|     local ($_, %_);
 | |
|     my ($self, $file, $FH, $PH, $CMD);
 | |
|     ($self, $file, $FH) = @_;
 | |
|     # Open the file if it is not opened yet
 | |
|     if (!defined $FH) {
 | |
|         print STDERR "  Opening file in read mode ... " if $VERBOSE > 2;
 | |
|         open $FH, "+<", $file           or die "$THIS_FILE: $file: $!";
 | |
|         binmode $FH                     or die "$THIS_FILE: $file: $!";
 | |
|         print STDERR "done\n" if $VERBOSE > 2;
 | |
|     } else {
 | |
|         flock $FH, LOCK_UN;
 | |
|     }
 | |
|     ($self->{"file"}, $self->{"FH"}) = ($file, $FH);
 | |
|     $EXEC = where_is "xz" if !defined $EXEC;
 | |
| 
 | |
|     @_ = ($EXEC, "-cdf");
 | |
|     @_ = map "\"$_\"", @_ if $^O eq "MSWin32";
 | |
|     $CMD = join " ", @_;
 | |
|     print STDERR "  Starting $CMD from file ... " if $VERBOSE > 2;
 | |
|     # Redirect STDIN to $FH
 | |
|     open STDIN, "<&", $FH               or die "$THIS_FILE: $file: $!";
 | |
|     # Start the process
 | |
|     if ($^O eq "MSWin32") {
 | |
|         open $PH, "$CMD |"              or die "$THIS_FILE: $CMD: $!";
 | |
|     } else {
 | |
|         open $PH, "-|", @_              or die "$THIS_FILE: $CMD: $!";
 | |
|     }
 | |
|     # Restore STDIN
 | |
|     open STDIN, "<&", $STDIN            or die "$THIS_FILE: STDIN: $!";
 | |
|     print STDERR "done\n" if $VERBOSE > 2;
 | |
|     ($self->{"CMD"}, $self->{"PH"}) = ([@_], $PH);
 | |
|     return;
 | |
| }
 | |
| 
 | |
| # Open the file for writing
 | |
| sub open_write : method {
 | |
|     local ($_, %_);
 | |
|     my ($self, $file, $FH, $PH, $CMD);
 | |
|     ($self, $file, $FH) = @_;
 | |
|     # Open the file if it is not opened yet
 | |
|     if (!defined $FH) {
 | |
|         print STDERR "  Creating file in write mode ... " if $VERBOSE > 2;
 | |
|         open $FH, "+>", $file           or die "$THIS_FILE: $file: $!";
 | |
|         binmode $FH                     or die "$THIS_FILE: $file: $!";
 | |
|         print STDERR "done\n" if $VERBOSE > 2;
 | |
|     } else {
 | |
|         flock $FH, LOCK_UN;
 | |
|     }
 | |
|     ($self->{"file"}, $self->{"FH"}) = ($file, $FH);
 | |
|     $EXEC = where_is "xz" if !defined $EXEC;
 | |
| 
 | |
|     @_ = ($EXEC, "-9f");
 | |
|     @_ = map "\"$_\"", @_ if $^O eq "MSWin32";
 | |
|     $CMD = join " ", @_;
 | |
|     print STDERR "  Starting $CMD to file ... " if $VERBOSE > 2;
 | |
|     # Redirect STDOUT to $FH
 | |
|     open STDOUT, ">&", $FH              or die "$THIS_FILE: $file: $!";
 | |
|     # Start the process
 | |
|     if ($^O eq "MSWin32") {
 | |
|         open $PH, "| $CMD"              or die "$THIS_FILE: $CMD: $!";
 | |
|     } else {
 | |
|         open $PH, "|-", @_              or die "$THIS_FILE: $CMD: $!";
 | |
|     }
 | |
|     # Restore STDOUT
 | |
|     open STDOUT, ">&", $STDOUT          or die "$THIS_FILE: STDOUT: $!";
 | |
|     print STDERR "done\n" if $VERBOSE > 2;
 | |
|     ($self->{"CMD"}, $self->{"PH"}) = ([@_], $PH);
 | |
|     return;
 | |
| }
 | |
| 
 | |
| # Open the file for appending
 | |
| sub open_append : method {
 | |
|     local ($_, %_);
 | |
|     my ($self, $file, $FH, $PH, $CMD);
 | |
|     ($self, $file, $FH) = @_;
 | |
|     # Open the file if it is not opened yet
 | |
|     if (!defined $FH) {
 | |
|         print STDERR "  Opening file in read/write mode ... " if $VERBOSE > 2;
 | |
|         open $FH, "+<", $file           or die "$THIS_FILE: $file: $!";
 | |
|         binmode $FH                     or die "$THIS_FILE: $file: $!";
 | |
|         print STDERR "done\n" if $VERBOSE > 2;
 | |
|     } else {
 | |
|         flock $FH, LOCK_UN;
 | |
|     }
 | |
|     ($self->{"file"}, $self->{"FH"}) = ($file, $FH);
 | |
|     $EXEC = where_is "xz" if !defined $EXEC;
 | |
| 
 | |
|     # Save the original data if file has content so that file size is
 | |
|     # greater than 0.  STDOUT is always of size 0.
 | |
|     if ((stat $FH)[7] > 0) {
 | |
|         my ($count, $FHT, $PHT, $CMD_T);
 | |
|         seek $FH, 0, SEEK_SET           or die "$THIS_FILE: $file: $!";
 | |
|         # Copy the original content to a buffer
 | |
|         print STDERR "  Reading compressed data to the buffer ... " if $VERBOSE > 2;
 | |
|         $FHT = tempfile                 or die "$THIS_FILE: tempfile: $!";
 | |
|         while (defined($_ = <$FH>)) {
 | |
|             print $FHT $_               or die "$THIS_FILE: tempfile: $!";
 | |
|         }
 | |
|         print STDERR "done\n" if $VERBOSE > 2;
 | |
|         print STDERR "  Restarting file ... " if $VERBOSE > 2;
 | |
|         seek $FHT, 0, SEEK_SET          or die "$THIS_FILE: tempfile: $!";
 | |
|         seek $FH, 0, SEEK_SET           or die "$THIS_FILE: $file: $!";
 | |
|         truncate $FH, 0                 or die "$THIS_FILE: $file: $!";
 | |
|         print STDERR "done\n" if $VERBOSE > 2;
 | |
| 
 | |
|         # Decompress the buffer and save to our file
 | |
|         @_ = ($EXEC, "-cdf");
 | |
|         @_ = map "\"$_\"", @_ if $^O eq "MSWin32";
 | |
|         $CMD_T = join " ", @_;
 | |
|         print STDERR "  Starting $CMD_T from buffer ... " if $VERBOSE > 2;
 | |
|         # Redirect STDIN to $FH
 | |
|         open STDIN, "<&", $FHT          or die "$THIS_FILE: tempfile: $!";
 | |
|         # Start the process
 | |
|         if ($^O eq "MSWin32") {
 | |
|             open $PHT, "$CMD_T |"        or die "$THIS_FILE: $CMD_T: $!";
 | |
|         } else {
 | |
|             open $PHT, "-|", @_         or die "$THIS_FILE: $CMD_T: $!";
 | |
|         }
 | |
|         # Restore STDIN
 | |
|         open STDIN, "<&", $STDIN        or die "$THIS_FILE: STDIN: $!";
 | |
|         print STDERR "done\n" if $VERBOSE > 2;
 | |
| 
 | |
|         @_ = ($EXEC, "-9f");
 | |
|         @_ = map "\"$_\"", @_ if $^O eq "MSWin32";
 | |
|         $CMD = join " ", @_;
 | |
|         print STDERR "  Starting $CMD to file ... " if $VERBOSE > 2;
 | |
|         # Redirect STDOUT to $FH
 | |
|         open STDOUT, ">&", $FH          or die "$THIS_FILE: $file: $!";
 | |
|         # Start the process
 | |
|         if ($^O eq "MSWin32") {
 | |
|             open $PH, "| $CMD"          or die "$THIS_FILE: $CMD: $!";
 | |
|         } else {
 | |
|             open $PH, "|-", @_          or die "$THIS_FILE: $CMD: $!";
 | |
|         }
 | |
|         # Restore STDOUT
 | |
|         open STDOUT, ">&", $STDOUT      or die "$THIS_FILE: STDOUT: $!";
 | |
|         print STDERR "done\n" if $VERBOSE > 2;
 | |
| 
 | |
|         print STDERR "  Reading old records back from the buffer ... " if $VERBOSE > 2;
 | |
|         $count = 0;
 | |
|         while (defined($_ = <$PHT>)) {
 | |
|             print $PH $_                or die "$THIS_FILE: $file: $!";
 | |
|             $count++;
 | |
|         }
 | |
|         close $PHT                      or die "$THIS_FILE: $CMD_T: $!";
 | |
|         close $FHT                      or die "$THIS_FILE: tempfile: $!";
 | |
|         print STDERR "$count records\n" if $VERBOSE > 2;
 | |
| 
 | |
|     # A whole new file
 | |
|     } else {
 | |
|         @_ = ($EXEC, "-9f");
 | |
|         @_ = map "\"$_\"", @_ if $^O eq "MSWin32";
 | |
|         $CMD = join " ", @_;
 | |
|         print STDERR "  Starting $CMD to file ... " if $VERBOSE > 2;
 | |
|         # Redirect STDOUT to $FH
 | |
|         open STDOUT, ">&", $FH          or die "$THIS_FILE: $file: $!";
 | |
|         # Start the process
 | |
|         if ($^O eq "MSWin32") {
 | |
|             open $PH, "| $CMD"          or die "$THIS_FILE: $CMD: $!";
 | |
|         } else {
 | |
|             open $PH, "|-", @_          or die "$THIS_FILE: $CMD: $!";
 | |
|         }
 | |
|         # Restore STDOUT
 | |
|         open STDOUT, ">&", $STDOUT      or die "$THIS_FILE: STDOUT: $!";
 | |
|         print STDERR "done\n" if $VERBOSE > 2;
 | |
|     }
 | |
| 
 | |
|     ($self->{"CMD"}, $self->{"PH"}) = ([@_], $PH);
 | |
|     return;
 | |
| }
 | |
| 
 | |
| # Read a line from the I/O stream
 | |
| sub readline : method {
 | |
|     local ($_, %_);
 | |
|     my ($self, $PH);
 | |
|     $self = $_[0];
 | |
|     $PH = $self->{"PH"};
 | |
|     return <$PH>;
 | |
| }
 | |
| 
 | |
| # Output data to the I/O stream
 | |
| sub write : method {
 | |
|     local ($_, %_);
 | |
|     my ($self, $CMD, $PH);
 | |
|     ($self, $_) = @_;
 | |
|     ($CMD, $PH) = (join(" ", @{$self->{"CMD"}}), $self->{"PH"});
 | |
|     print $PH $_                        or die "$THIS_FILE: $CMD: $!";
 | |
|     return;
 | |
| }
 | |
| 
 | |
| # Close the I/O stream
 | |
| sub close : method {
 | |
|     local ($_, %_);
 | |
|     my ($self, $keep, $tmp, $file, $FH, $CMD, $PH);
 | |
|     ($self, $keep, $tmp) = @_;
 | |
|     $keep = KEEP_ALL if @_ < 2;
 | |
|     ($file, $FH) = ($self->{"file"}, $self->{"FH"});
 | |
| 
 | |
|     # Restart the file
 | |
|     if ($keep eq KEEP_RESTART || $keep eq KEEP_THIS_MONTH) {
 | |
|         my ($CMD, $PH);
 | |
|         # Empty the source file
 | |
|         print STDERR "  Emptying file ... " if $VERBOSE > 2;
 | |
|         seek $FH, 0, SEEK_SET           or die "$THIS_FILE: $file: $!";
 | |
|         truncate $FH, 0                 or die "$THIS_FILE: $file: $!";
 | |
|         print STDERR "done\n" if $VERBOSE > 2;
 | |
| 
 | |
|         # Create empty compressed content
 | |
|         print STDERR "  Applying empty compressed content ... " if $VERBOSE > 2;
 | |
|         $EXEC = where_is "xz" if !defined $EXEC;
 | |
|         @_ = ($EXEC, "-9f");
 | |
|         @_ = map "\"$_\"", @_ if $^O eq "MSWin32";
 | |
|         $CMD = join " ", @_;
 | |
|         # Redirect STDOUT to $FH
 | |
|         open STDOUT, ">&", $FH          or die "$THIS_FILE: $file: $!";
 | |
|         # Start the process and end it
 | |
|         if ($^O eq "MSWin32") {
 | |
|             open $PH, "| $CMD"          or die "$THIS_FILE: $CMD: $!";
 | |
|         } else {
 | |
|             open $PH, "|-", @_          or die "$THIS_FILE: $CMD: $!";
 | |
|         }
 | |
|         close $PH                       or die "$THIS_FILE: $CMD: $!";
 | |
|         # Restore STDOUT
 | |
|         open STDOUT, ">&", $STDOUT      or die "$THIS_FILE: STDOUT: $!";
 | |
|         print STDERR "done\n" if $VERBOSE > 2;
 | |
|     }
 | |
| 
 | |
|     ($CMD, $PH) = (join(" ", @{$self->{"CMD"}}), $self->{"PH"});
 | |
|     CORE::close $PH                     or die "$THIS_FILE: $CMD: $!";
 | |
|     CORE::close $FH                     or die "$THIS_FILE: $file: $!";
 | |
|     delete $self->{"PH"};
 | |
|     delete $self->{"CMD"};
 | |
|     delete $self->{"FH"};
 | |
|     delete $self->{"file"};
 | |
| 
 | |
|     # Delete the file
 | |
|     if ($keep eq KEEP_DELETE) {
 | |
|         print STDERR "  Deleting file ... " if $VERBOSE > 2;
 | |
|         unlink $file                    or die "$THIS_FILE: $file: $!";
 | |
|         print STDERR "done\n" if $VERBOSE > 2;
 | |
|     }
 | |
|     # Delete the temporary file if needed
 | |
|     if (defined $tmp && -e $tmp) {
 | |
|         unlink $tmp                     or die "$THIS_FILE: $tmp: $!";
 | |
|     }
 | |
|     return;
 | |
| }
 | |
| 
 | |
| 
 | |
| # _private::Format: The abstract log file format handler interface
 | |
| package _private::Format;
 | |
| use 5.008;
 | |
| use strict;
 | |
| use warnings;
 | |
| BEGIN {
 | |
| import main;
 | |
| }
 | |
| 
 | |
| use Date::Parse qw(str2time);
 | |
| 
 | |
| # Initialize the log record format parser
 | |
| sub new : method { bless {}, $_[0]; }
 | |
| 
 | |
| # Check the record format and return an appropriate parser
 | |
| sub check_format : method {
 | |
|     local ($_, %_);
 | |
|     my (@formats, $record);
 | |
|     $record = $_[1];
 | |
|     @formats = qw(_private::Format::Apache _private::Format::Syslog
 | |
|         _private::Format::NTP _private::Format::ApacheSSL
 | |
|         _private::Format::MyISO);
 | |
|     foreach my $format (@formats) {
 | |
|         $_ = $format->new;
 | |
|         print STDERR "  Testing $_ ... " if $VERBOSE > 2;
 | |
|         if ($_->match($record)) {
 | |
|             print STDERR "match\n" if $VERBOSE > 2;
 | |
|             return $_;
 | |
|         }
 | |
|         print STDERR "not match\n" if $VERBOSE > 2;
 | |
|     }
 | |
|     return undef;
 | |
| }
 | |
| 
 | |
| # Try matching my format and return the matching date text
 | |
| #   Empty.  Implement it in the subclasses.
 | |
| sub match : method { return undef; }
 | |
| 
 | |
| # Parse the month of the log file
 | |
| sub parse_month : method {
 | |
|     local ($_, %_);
 | |
|     my $self;
 | |
|     ($self, $_) = @_;
 | |
|     return undef unless defined($_ = $self->match($_));
 | |
|     return undef unless defined($_ = str2time $_);
 | |
|     return to_yyyymm $_;
 | |
| }
 | |
| 
 | |
| 
 | |
| # _private::Format::Apache: The Apache log file format handler
 | |
| package _private::Format::Apache;
 | |
| use 5.008;
 | |
| use strict;
 | |
| use warnings;
 | |
| use base qw(_private::Format);
 | |
| use overload ("\"\"" => sub { "Apache access_log"; });
 | |
| 
 | |
| # Try matching my format and return the matching date text
 | |
| sub match : method {
 | |
|     return $_[1] =~ /^\S+ \S+ .*? \[(\d{2}\/[A-Z][a-z]{2}\/\d{4}:\d{2}:\d{2}:\d{2} [+\-]\d{4})\]/?
 | |
|         $1: undef;
 | |
| }
 | |
| 
 | |
| 
 | |
| # _private::Format::Syslog: The Syslog log file format handler
 | |
| package _private::Format::Syslog;
 | |
| use 5.008;
 | |
| use strict;
 | |
| use warnings;
 | |
| use base qw(_private::Format);
 | |
| use overload ("\"\"" => sub { "Syslog"; });
 | |
| 
 | |
| # Try matching my format and return the matching date text
 | |
| sub match : method {
 | |
|     return $_[1] =~ /^([A-Z][a-z]{2}  ?\d{1,2} \d{2}:\d{2}:\d{2}) /?
 | |
|         $1: undef;
 | |
| }
 | |
| 
 | |
| 
 | |
| # _private::Format::NTP: The NTP log file format handler
 | |
| package _private::Format::NTP;
 | |
| use 5.008;
 | |
| use strict;
 | |
| use warnings;
 | |
| use base qw(_private::Format);
 | |
| use overload ("\"\"" => sub { "NTP"; });
 | |
| 
 | |
| # Try matching my format and return the matching date text
 | |
| sub match : method {
 | |
|     return $_[1] =~ /^( ?\d{1,2} [A-Z][a-z]{2} \d{2}:\d{2}:\d{2}) /?
 | |
|         $1: undef;
 | |
| }
 | |
| 
 | |
| 
 | |
| # _private::Format::ApacheSSL: The Apache ssl_engine_log log file format handler
 | |
| package _private::Format::ApacheSSL;
 | |
| use 5.008;
 | |
| use strict;
 | |
| use warnings;
 | |
| use base qw(_private::Format);
 | |
| use overload ("\"\"" => sub { "Apache SSL engine log"; });
 | |
| 
 | |
| # Try matching my format and return the matching date text
 | |
| sub match : method {
 | |
|     return $_[1] =~ /^\[(\d{2}\/[A-Z][a-z]{2}\/\d{4} \d{2}:\d{2}:\d{2} )\d+\]/?
 | |
|         $1: undef;
 | |
| }
 | |
| 
 | |
| 
 | |
| # _private::Format::MyISO: The bracketed, modified ISO 8601 date/time log file format handler
 | |
| #   ISO 8601 should be 2007-11-14T14:23:35+0800.  But it is hard to read.
 | |
| #   This is a similar format commonly-seen in many applications.
 | |
| package _private::Format::MyISO;
 | |
| use 5.008;
 | |
| use strict;
 | |
| use warnings;
 | |
| use base qw(_private::Format);
 | |
| use overload ("\"\"" => sub { "modified ISO 8601 date/time"; });
 | |
| 
 | |
| # Try matching my format and return the matching date text
 | |
| sub match : method {
 | |
|     return $_[1] =~ /^\[(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} [+\-]\d{4})\]/?
 | |
|         $1: undef;
 | |
| }
 | |
| 
 | |
| 
 | |
| __END__
 | |
| 
 | |
| =head1 NAME
 | |
| 
 | |
| arclog - Archive the log files monthly
 | |
| 
 | |
| =head1 SYNOPSIS
 | |
| 
 | |
|  arclog [options] logfile... [output]
 | |
|  arclog [-h|-v]
 | |
| 
 | |
| =head1 DESCRIPTION
 | |
| 
 | |
| F<arclog> archives the log files monthly.  It strips off log entries
 | |
| that belongs to previous months, and then compresses and saves them
 | |
| to archived files named logfile.yyyymm.gz.
 | |
| 
 | |
| Currently, F<arclog> supports Apache access log, Syslog, NTP, Apache
 | |
| 1 SSL engine log, and my own bracketed, modified ISO date/time log
 | |
| file formats, and gzip, bzip2, and xz compression methods.  Several
 | |
| software projects log (or can log) in a format compatible with the
 | |
| Apache access log, like CUPS, ProFTPD, Pure-FTPd... etc., and
 | |
| F<arclog> can archive their Apache-like log files, too.
 | |
| 
 | |
| Notice: I<Archival takes time>.  To reduce the time occupying the
 | |
| source log file, F<arclog> copies the content of the source log
 | |
| file to a temporary working file and restart the source log file
 | |
| first.  Then F<arclog> can take its time working on the temporary
 | |
| working file.  However, please note:
 | |
| 
 | |
| 1. If you have a huge log file (several hundreds of MBs), merely
 | |
| copying still takes a lot of time.  In that case, you had better stop
 | |
| logging first, archive the log file and restart logging, to avoid
 | |
| racing condition in writing.  If you archive the log file
 | |
| periodically, it shall not grow too big.
 | |
| 
 | |
| 2. If F<arclog> stops in the middle of the execution, it will leave
 | |
| a temporary working file.  The next time F<arclog> runs, it stops
 | |
| when it sees that temporary working file.  You have to process that
 | |
| temporary working file first.  That temporary working file is merely
 | |
| a copy of the original log file.  You can rename and archive it like
 | |
| an ordinary log file to solve this.
 | |
| 
 | |
| Do not sort unless you have a particular reason.  Sorting has the
 | |
| following potential problems:
 | |
| 
 | |
| 1. Sorting may I<eat huge memory> on large log files.  The amount of
 | |
| the memory required depends on the number of records in each archived
 | |
| month.  Modern Linux and MSWin32 kill processes that eat too much
 | |
| memory, but it still takes minutes, and your system hangs for that.  I
 | |
| do not know other operating systems.  Try at your own risk.
 | |
| 
 | |
| 2. The time unit of all recognized log formats is I<second>.  Log
 | |
| records happen in a same second are sorted by the log file order (if
 | |
| you are archiving several log files at a time) and then the log record
 | |
| order.  I try to ensure that the sorted archived records are in a
 | |
| correct order of the happening events, but I cannot guarantee.  You
 | |
| have to watch out if the order in a second is important.
 | |
| 
 | |
| Be careful on the L<Syslog(2)|syslog/2> and NTP log files:
 | |
| L<Syslog(2)|syslog/2> and NTP does not record the year.  F<arclog>
 | |
| uses L<Date::Parse(3)|Date::Parse/3> to parse the date, which assumes
 | |
| the year between this month and last next month if the year is
 | |
| missing.  For example, if today is 2001/6/8, it assumes the year
 | |
| between 2001/6/30 back to 2000/7/1 if the year is missing.  This is
 | |
| fair.  However, if you do have a L<Syslog(2)|syslog/2> or NTP log file
 | |
| that has records older than one year, do not use F<arclog>.  It will
 | |
| destroy your log file.
 | |
| 
 | |
| If read from C<STDIN>, please note:
 | |
| 
 | |
| 1. You I<MUST> specify the output prefix if you want to read from
 | |
| C<STDIN>, since what it needs is an output pathname prefix, not an
 | |
| output file.
 | |
| 
 | |
| 2. C<STDIN> cannot be deleted, restarted or partially kept.  If you
 | |
| read from C<STDIN>, the keep mode is always keep all.  if you archive
 | |
| several source log files including C<STDIN>, the keep mode will be
 | |
| keep all for all source log files, to prevent disaster.
 | |
| 
 | |
| 3. The answers of the C<ask> mode is obtained from C<STDIN>, too.
 | |
| Since you have only one C<STDIN>, you cannot specify the C<ask> mode
 | |
| while reading from C<STDIN>.  It falls back to the C<fail> mode
 | |
| in that case.
 | |
| 
 | |
| I suggest that you install L<File::MMagic(3)|File::MMagic/3> instead
 | |
| of counting on the L<file(1)|file/1> executable.  The internal magic
 | |
| file of L<File::MMagic(3)|File::MMagic/3> works better than the
 | |
| L<file(1)|file/1> executable.  F<arclog> treats everything not
 | |
| L<gzip(1)|gzip/1>, L<bzip2(1)|bzip2/1>, nor L<xz(1)|xz/1> compressed
 | |
| as plain text.  When a compressed log file is wrongly recognized as
 | |
| an image, F<arclog> treats it as plain text, reads directly from it,
 | |
| and fails.  This does not hurt the source log files, but is still
 | |
| annoying.
 | |
| 
 | |
| =head1 OPTIONS
 | |
| 
 | |
| =over
 | |
| 
 | |
| =item logfile
 | |
| 
 | |
| The log file to be archived.  Specify C<-> to read from C<STDIN>.
 | |
| You can specify multiple log files.  L<gzip(1)|gzip/1>,
 | |
| L<bzip2(1)|bzip2/1>, or L<xz(1)|xz/1> compressed files are supported.
 | |
| 
 | |
| =item output
 | |
| 
 | |
| The prefix of the output files.  The output files are named as
 | |
| F<output.yyyymm>, ie: F<output.200101>, F<output.200101>.  If not
 | |
| specified, the default is the same as the log file.  You must specify
 | |
| this if you want to read from C<STDIN>.  You cannot specify C<->
 | |
| (C<STDOUT>), since F<arclog> needs a name prefix, not the output file.
 | |
| 
 | |
| =item -c, --compress method
 | |
| 
 | |
| Specify the compression method for the archived files.  Log files
 | |
| usually have large number of similar lines.  Compress them saves
 | |
| you lots of disk spaces.  (And this is why we want to I<archive>
 | |
| them.)  The following compression methods are supported:
 | |
| 
 | |
| =over
 | |
| 
 | |
| =item g, gzip
 | |
| 
 | |
| Compress with L<gzip(1)|gzip/1>.  This is the default.  F<arclog>
 | |
| can use L<Compress::Zlib(3)|Compress::Zlib/3> to compress instead of
 | |
| calling L<gzip(1)|gzip/1>.  This can be safer and faster for not
 | |
| calling foreign binaries.  if L<Compress::Zlib(3)|Compress::Zlib/3>
 | |
| is not installed, it tries L<gzip(1)|gzip/1> instead.  If
 | |
| L<gzip(1)|gzip/1> is not available, either, it fails.
 | |
| 
 | |
| =item b, bzip2
 | |
| 
 | |
| Compress with L<bzip2(1)|bzip2/1>.  F<arclog> can use
 | |
| L<Compress::Bzip2(3)|Compress::Bzip2/3> to compress instead of
 | |
| calling L<bzip2(1)|bzip2/1>.  This can be safer and faster for not
 | |
| calling foreign binaries.  If L<Compress::Bzip2(3)|Compress::Bzip2/3>
 | |
| is not installed, it will try to use L<bzip2(1)|bzip2/1> instead.  If
 | |
| L<bzip2(1)|bzip2/1> is not available, either, it fails.
 | |
| 
 | |
| =item x, xz
 | |
| 
 | |
| Compress with L<xz(1)|xz/1>.  F<arclog> can use
 | |
| L<IO::Compress::Xz(3)|IO::Compress::Xz/3> to compress instead of
 | |
| calling L<xz(1)|xz/1>.  This can be safer and faster for not
 | |
| calling foreign binaries.  If L<IO::Compress::Xz(3)|IO::Compress::Xz/3>
 | |
| is not installed, it will try to use L<xz(1)|xz/1> instead.  If
 | |
| L<xz(1)|xz/1> is not available, either, it fails.
 | |
| 
 | |
| =item n, none
 | |
| 
 | |
| No compression at all.  (Why? :p)
 | |
| 
 | |
| =back
 | |
| 
 | |
| =item --nocompress
 | |
| 
 | |
| Do not compress the archived files.  This is equivalent to
 | |
| C<--compress none>.
 | |
| 
 | |
| =item -s, --sort
 | |
| 
 | |
| Sort the records by time (and then the record order).  Sorting eats
 | |
| huge memory and CPU, so it is disabled by default.  Refer to the
 | |
| description above for a detailed illustration on sorting.
 | |
| 
 | |
| =item --nosort
 | |
| 
 | |
| Do not sort the records.  This is the default.
 | |
| 
 | |
| =item -o, --override mode
 | |
| 
 | |
| What to do with the existing archived files.  The following modes are
 | |
| supported:
 | |
| 
 | |
| =over
 | |
| 
 | |
| =item o, overwrite
 | |
| 
 | |
| Overwrite existing target files.  You will lose these existing
 | |
| records.  Use with care.  This is helpful if you are sure the master
 | |
| log file has the most complete records.
 | |
| 
 | |
| =item a, append
 | |
| 
 | |
| Append the records to the existing target files.  You may destroy the
 | |
| log file completely by putting irrelevant entries altogether
 | |
| by accident.  Use with care.  This is helpful if you append want to
 | |
| merge 2 or more log files, for example, 2 log files of different
 | |
| periods.
 | |
| 
 | |
| =item i, ignore
 | |
| 
 | |
| Ignore any existing target file, and discard all the records of those
 | |
| months.  You will lose these log records.  Use with care.  This is
 | |
| helpful if you are supplying log records for the missing months, or
 | |
| if you are merging the log records in a complex manner.
 | |
| 
 | |
| =item f, fail
 | |
| 
 | |
| Stop whenever a target file exists, to prevent destroying existing
 | |
| files by accident.  This should be mostly desired when run from some
 | |
| automatic mechanism, like L<crontab(1)|crontab/1>.  So, this is the
 | |
| default if no terminal is found at C<STDIN>.
 | |
| 
 | |
| =item ask
 | |
| 
 | |
| Ask you what to do when a target file exists.  This should be most
 | |
| wanted if you are running F<arclog> interactively.  So, this is the
 | |
| default if a terminal is found at C<STDIN>.  The answers are read
 | |
| from C<STDIN>.  Since you have only one C<STDIN>, you cannot specify
 | |
| this mode if you want read the log file from C<STDIN>.  In that case,
 | |
| it falls back to the C<fail> mode.  Also, if F<arclog> cannot get its
 | |
| answer from C<STDIN>, for example, on a closed C<STDIN> from
 | |
| L<crontab(1)|crontab/1>, it falls back to C<fail> mode.
 | |
| 
 | |
| =back
 | |
| 
 | |
| =item -k, --keep mode
 | |
| 
 | |
| What to keep in the source file.  The following modes are supported:
 | |
| 
 | |
| =over
 | |
| 
 | |
| =item a, all
 | |
| 
 | |
| Keep the source file after records are archived.
 | |
| 
 | |
| =item r, restart
 | |
| 
 | |
| Restart the source file after records are archived.
 | |
| 
 | |
| =item d, delete
 | |
| 
 | |
| Delete the source file after records are archived.
 | |
| 
 | |
| =item t, this-month
 | |
| 
 | |
| Archive and strip records of previous months off from the log file.
 | |
| Keep the records of this month in the source log file, to be archived
 | |
| next month.  This is designed to be run from L<crontab(1)|crontab/1>
 | |
| monthly, so this is the default.
 | |
| 
 | |
| =back
 | |
| 
 | |
| =item -d, --debug
 | |
| 
 | |
| Show the detailed debugging messages.
 | |
| 
 | |
| =item -q, --quiet
 | |
| 
 | |
| Hush!  Only yell on error.
 | |
| 
 | |
| =item -h, --help
 | |
| 
 | |
| Display the help message and exit.
 | |
| 
 | |
| =item -v, --version
 | |
| 
 | |
| Output version information and exit.
 | |
| 
 | |
| =back
 | |
| 
 | |
| =head1 COPYRIGHT
 | |
| 
 | |
| Copyright (c) 2001-2021 imacat.
 | |
| 
 | |
| Licensed under the Apache License, Version 2.0 (the "License");
 | |
| you may not use this file except in compliance with the License.
 | |
| You may obtain a copy of the License at
 | |
| 
 | |
|     http://www.apache.org/licenses/LICENSE-2.0
 | |
| 
 | |
| Unless required by applicable law or agreed to in writing, software
 | |
| distributed under the License is distributed on an "AS IS" BASIS,
 | |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
| See the License for the specific language governing permissions and
 | |
| limitations under the License.
 | |
| 
 | |
| =head1 AUTHOR
 | |
| 
 | |
| imacat <imacat@mail.imacat.idv.tw>.
 | |
| 
 | |
| =head1 BUGS
 | |
| 
 | |
| The F<arclog> project is hosted on GitHub.  Address your issues on the
 | |
| GitHub issue tracker https://github.com/imacat/arclog/issues.
 | |
| 
 | |
| =head1 TODO
 | |
| 
 | |
| =over
 | |
| 
 | |
| =item Multi-lingual support
 | |
| 
 | |
| Multi-lingual support, with Traditional and Simplified Chinese
 | |
| messages.
 | |
| 
 | |
| =back
 | |
| 
 | |
| =head1 SEE ALSO
 | |
| 
 | |
| L<gzip(1)|gzip/1>, L<bzip2(1)|bzip2/1>, L<xz(1)|xz/1>,
 | |
| L<Compress::Zlib(3)|Compress::Zlib/3>, L<syslog(1)|syslog/1>,
 | |
| L<Compress::Bzip2(3)|Compress::Bzip2/3>,
 | |
| L<IO::Compress::Xz(3)|IO::Compress::Xz/3>,
 | |
| L<IO::Uncompress::UnXz(3)|IO::Uncompress::UnXz/3>,
 | |
| L<syslog(2)|syslog/2>
 | |
| 
 | |
| =cut
 |