#!/usr/bin/perl
use strict;
use warnings;
use Carp qw(croak);
use Term::ANSIColor qw(colored);
use Getopt::Long qw(:config posix_default no_ignore_case bundling auto_help);
use Time::HiRes qw(time usleep ualarm);
USR1
$SIG{USR1} = &guess_current_status;
kill -USR1 %1 OK
# for USR1
sub guess_current_status {
my $signal = shift;
printf colored("dry-run mode. all delete operations are fake.n", "blue") if $dry_run;
printf "process id: %dn", $$;
printf "delete file count: %sn", comma($delete_count);
printf "delete size total: %s bytesn", comma($delete_size_total);
printf "process run times: %s secondn", comma(int(time - $process_start_time));
printf "interval=%d timeout=%sn", $interval, $timeout;
}
use constant DEBUG => $ENV{DEBUG};
use constant MAX_TIME_HIRES_BIT => 31;
use constant UNLINK_CHUNK_NUM => 20;
use constant COLOR_WARNING => "yellow";
use constant COLOR_ERROR => "red";
use constant COLOR_INFO => "green";
$SIG{USR1} = &guess_current_status;
GetOptions(
my %opt,
"timeout|t=f", "progress|p", "interval|i=f", "flexible|f", "dry-run|n"
);
my $timeout = $opt{timeout} || 0;
my $progress = $opt{progress};
my $interval = $opt{interval} || 0;
my $dry_run = $opt{"dry-run"};
my $flexible = $opt{flexible};
my $delete_count = 0;
my $delete_size_total = 0;
my $timeout_flag;
my $preserve_interval_flag;
my $process_start_time = time;
eval
local $@;
eval {
local $SIG{ALRM} = $timeout > 0 ? sub { $timeout_flag = 1; } : "DEFAULT";
# alarm (10^{-6}) 32 alarm
my $alarm_cancel_cb; # alram 0 ualarm 0
my $timeout_microsecond = $timeout * 10**6;
if ( bit($timeout_microsecond) < MAX_TIME_HIRES_BIT ) {
ualarm($timeout_microsecond);
$alarm_cancel_cb = sub { ualarm 0; };
} else {
alarm(int($timeout));
$alarm_cancel_cb = sub { alarm 0; };
}
gentle_unlink();
$alarm_cancel_cb->();
};
if ( $@ ) {
print colored("e=$@n", COLOR_ERROR);
}
sub gentle_unlink {
my @file_chunk;
my $start_time = time;
while (<>) {
chomp;
print "> $_n" if DEBUG;
push @file_chunk, $_;
if ( @file_chunk > UNLINK_CHUNK_NUM ) {
unlink_chunk(@file_chunk);
@file_chunk = ();
$preserve_interval_flag = 1;
}
} continue {
if ( $timeout_flag ) {
progress_printf(colored("timeoutn", COLOR_WARNING));
last;
}
if ( $interval && $preserve_interval_flag ) {
progress_printf(colored("interval %f seconds.n", COLOR_INFO), $interval );
usleep $interval * 10**6;
$preserve_interval_flag = 0;
}
}
if ( @file_chunk ) {
unlink_chunk(@file_chunk);
}
my $end_time = time;
progress_printf("%f seconds, %s bytes, total %d files deleted.n",
$end_time - $start_time, comma($delete_size_total), $delete_count);
progress_printf(colored("dry-run mode. all delete operations are fake.n", "blue")) if
$dry_run;
}
sub unlink_chunk {
my @files = @_;
my @nowhere_files = grep { !-f } @files;
if ( @nowhere_files ) {
if ( $flexible ) {
progress_printf(colored(
join( "", map { qq(file "$_" is not foundn) } @nowhere_files ),
COLOR_WARNING
)); # progress_printf colored
} else {
croak colored("unlink_chunk gives lost filename.", COLOR_ERROR);
}
}
progress_printf(">>> dry-run moden") if $dry_run;
progress_printf("%4d: deleting... %s (%s bytes)n",
++$delete_count, $_, comma(-s $_)) for @files;
$delete_size_total += -s $_ for @files;
return if $dry_run;
local $!;
my $chunk_num = @files;
my $delete_num = unlink @files;
if ( $chunk_num != $delete_num ) {
print colored("maybe delete failed.n" . "w=$!", COLOR_ERROR);
croak colored("delete failure error", COLOR_ERROR) if !$flexible;
}
}
sub comma {
my $number = shift;
$number =~ s/(?<=d)(?=(?:ddd)+(?!d))/,/g;
return $number;
}
sub bit {
my $integer = shift;
return 0 if $integer == 0;
return( log($integer) / log(2) );
}