File Coverage

blib/lib/Util/SelfDestruct.pm
Criterion Covered Total %
statement 55 100 55.0
branch 13 56 23.2
condition 6 51 11.7
subroutine 14 19 73.6
pod 0 2 0.0
total 88 228 38.6


line stmt bran cond sub pod time code
1             ############################################################
2             #
3             # $Id: SelfDestruct.pm,v 1.20 2006/01/12 22:45:11 nicolaw Exp $
4             # Util::SelfDestruct - Conditionally prevent execution of a script
5             #
6             # Copyright 2005,2006 Nicola Worthington
7             #
8             # Licensed under the Apache License, Version 2.0 (the "License");
9             # you may not use this file except in compliance with the License.
10             # You may obtain a copy of the License at
11             #
12             # http://www.apache.org/licenses/LICENSE-2.0
13             #
14             # Unless required by applicable law or agreed to in writing, software
15             # distributed under the License is distributed on an "AS IS" BASIS,
16             # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17             # See the License for the specific language governing permissions and
18             # limitations under the License.
19             #
20             ############################################################
21              
22             package Util::SelfDestruct;
23             # vim:ts=4:sw=4:tw=78
24              
25             BEGIN {
26 1     1   7436 use strict;
  1         2  
  1         45  
27 1     1   5 use Carp qw(cluck croak);
  1         2  
  1         63  
28 1     1   5 use Cwd qw(abs_path);
  1         2  
  1         41  
29 1     1   5 use Fcntl qw(:DEFAULT :flock);
  1         1  
  1         495  
30              
31 1 50   1   5 use constant DEBUG => $ENV{'DEBUG'} ? 1 : 0;
  1         2  
  1         83  
32 1 50   1   5 use constant PROGRAM_NAME => -e abs_path($0) ? abs_path($0) : undef;
  1         1  
  1         144  
33 1 50   1   4 use constant HOME => -d (getpwuid($>))[7] ? (getpwuid($>))[7] : $ENV{HOME};
  1         1  
  1         1113  
34 1     1   6 use constant RC_FILE => HOME.'/.selfdestruct';
  1         1  
  1         43  
35              
36 1     1   5 use vars qw($VERSION $PARAM);
  1         2  
  1         84  
37 1     1   3 $VERSION = '1.21' || sprintf('%d.%02d', q$Revision$ =~ /(\d+)/g);
38 1         1320 $PARAM = {};
39             }
40              
41             END {
42             if (my ($action,$context) = _whatActionToTake($PARAM)) {
43             if ($action eq 'unlink' && !exists $PARAM->{ABORT}) {
44             if (unlink(PROGRAM_NAME)) {
45             cluck(__PACKAGE__.": $context");
46             } else {
47             croak(sprintf('Failed to unlink %s during self destruct: %s',
48             PROGRAM_FILE,$!));
49             }
50             }
51             }
52             }
53              
54             sub import {
55 0     0   0 my $class = shift;
56              
57 0         0 my %alias = (
58             'delete' => 'unlink',
59             'erase' => 'unlink',
60             );
61 0         0 my %struct = (
62             'unlink' => 'bool',
63             'after' => 'value',
64             'before' => 'value',
65             );
66              
67 0         0 while (my $k = lc(shift(@_))) {
68 0 0       0 $k = $alias{$k} if exists $alias{$k};
69 0 0       0 if ($struct{$k} eq 'bool') {
70 0         0 $PARAM->{$k}++;
71             } else {
72 0         0 $PARAM->{$k} = lc(shift(@_));
73 0 0       0 if ($k eq 'before') {
    0          
74 0         0 $PARAM->{$k} = _mungeDateTime($PARAM->{$k},'000000');
75             } elsif ($k eq 'after') {
76 0         0 $PARAM->{$k} = _mungeDateTime($PARAM->{$k},'235959');
77             }
78 0 0       0 delete $PARAM->{$k} unless defined $PARAM->{$k};
79             }
80             }
81              
82 0 0 0     0 if ((exists $PARAM->{'before'} || exists $PARAM->{'after'}) &&
      0        
83             exists $PARAM->{'now'}) {
84 0         0 $PARAM->{ABORT}++;
85 0         0 croak "The 'now' flag cannot be used in conjunction with the ",
86             "'before' or 'after' options";
87             }
88              
89 0         0 DUMP('$PARAM',$PARAM);
90              
91 0 0       0 if (my ($action,$context) = _whatActionToTake($PARAM)) {
92 0 0       0 if ($action eq 'die') {
93 0         0 croak(__PACKAGE__.": $context");
94             }
95             }
96 0 0       0 _writeExecHistory() unless exists $PARAM->{'unlink'};
97             }
98              
99             sub _writeExecHistory {
100 0     0   0 return _processExecHistory('write');
101             }
102              
103             sub _readExecHistory {
104 1     1   2 return _processExecHistory('read');
105             }
106              
107             sub _processExecHistory {
108 1   50 1   4 my $action = shift || 'read';
109              
110 1         1 my $matchInFile = 0;
111 1         2 my $programName = PROGRAM_NAME;
112 1 50       18 my $mode = (-e RC_FILE ? '+<' : '+>');
113              
114 1 50 33     42 if (open(FH,$mode,RC_FILE) && flock(FH,LOCK_EX)) {
115             #seek(FH, 0, 0);
116 1         27 while (my $str = ) {
117 6         7 chomp $str;;
118 6 50       21 if ($str eq $programName) {
119 0         0 $matchInFile++;
120 0         0 last;
121             }
122             }
123 1 50 33     4 if ($action eq 'write' && !$matchInFile) {
124 0         0 print FH "$programName\n";
125             }
126 1 50 33     19 (flock(FH,LOCK_UN) && close(FH)) ||
127             cluck(sprintf("Unable to close file handle FH for file %s: %s", RC_FILE,$!));
128              
129             } else {
130 0         0 croak(sprintf("Unable to open file handle FH with exclusive lock for file '%s': %s",
131             RC_FILE,$!));
132             }
133              
134 1         4 return $matchInFile;
135             }
136              
137             sub _whatActionToTake {
138 1     1   2 my $param = shift;
139 1 50       6 return undef if $param->{ABORT};
140              
141 1         3 my $context = '';
142 1 50       4 my $action = exists $param->{'unlink'} ? 'unlink' : 'die';
143 1         7 my $now = _unixtime2isodate(time());
144              
145             # No specific timing
146 1 50 33     13 if (!exists $param->{'after'} && !exists $param->{'before'}) {
    0 0        
    0 0        
    0 0        
      0        
      0        
      0        
      0        
147 1 50       4 if (exists $param->{'unlink'}) {
    50          
148 0         0 $context = 'unlink after execution';
149             } elsif (_readExecHistory() > 0) {
150 0         0 $context = 'die on subsequent execution (only allow execution once)';
151             } else {
152 1         2 $action = '';
153             }
154              
155             } elsif ((exists $param->{'after'} && exists $param->{'before'})
156             && $now > $param->{'after'} && $now < $param->{'before'}) {
157 0         0 $context = "$now > $param->{after} and $now < $param->{before}";
158              
159             } elsif ((exists $param->{'after'} && !exists $param->{'before'})
160             && $now > $param->{'after'}) {
161 0         0 $context = "$now > $param->{after}";
162              
163             } elsif ((exists $param->{'before'} && !exists $param->{'after'})
164             && $now < $param->{'before'}) {
165 0         0 $context = "$now < $param->{before}";
166              
167             } else {
168 0         0 $action = '';
169             }
170              
171 1         4 return ($action,$context);
172             }
173              
174             sub _mungeDateTime {
175 0   0 0   0 my $str = shift || '';
176 0   0     0 my $padding = shift || '000000';
177              
178 0         0 (my $isodate = $str) =~ s/\D//g;
179 0 0 0     0 if ((length($str) - length($isodate) < 10) &&
180             (my ($year,$mon,$mday,$hour,$min,$sec) =
181             $isodate =~ /^\s*(19\d{2}|2\d{3})(0[1-9]|1[12])(0[1-9]|[12][0-9]|3[01])
182             (?:([01][0-9]|2[0-3])([0-5][0-9])([0-5][0-9]))?\s*$/x)) {
183 0 0       0 if (defined $hour) {
    0          
184 0         0 return $isodate;
185             } elsif ($padding =~ /^([01][0-9]|2[0-3])([0-5][0-9])([0-5][0-9])$/) {
186 0         0 return "$isodate$padding";
187             }
188             }
189              
190 0         0 return undef;
191             }
192              
193             sub _unixtime2isodate {
194 1   33 1   111 my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)
195             = localtime(shift() || time());
196 1         3 $year += 1900; $mon++;
  1         1  
197 1         6 my $isodate = sprintf('%04d%02d%02d%02d%02d%02d',
198             $year,$mon,$mday,$hour,$min,$sec);
199 1         3 return $isodate;
200             }
201              
202             sub TRACE {
203 0     0 0   return unless DEBUG;
204 0           warn(shift());
205             }
206              
207             sub DUMP {
208 0     0 0   return unless DEBUG;
209 0           eval {
210 0           require Data::Dumper;
211 0           warn(shift().': '.Data::Dumper::Dumper(shift()));
212             }
213             }
214              
215             1;
216              
217             =pod
218              
219             =head1 NAME
220              
221             Util::SelfDestruct - Conditionally prevent execution of a script
222              
223             =head1 SYNOPSIS
224              
225             # Immediately prevent execution of script by dying on invocation
226             # if it has already been executed once before. (The default behavior
227             # is to self destruct by dying, unless instructed otherwise).
228             use Util::SelfDestruct;
229            
230             # Delete the script after it is executed
231             use Util::SelfDestruct('unlink');
232            
233             # Prevent execution of the script by dying if it
234             # is executed after Dec 17th 2005 at 6pm
235             use Util::SelfDestruct(after => '2005-12-17 18h00m00s');
236            
237             # Delete the script after execution, if it is executed
238             # between 1st Dec 2005 and 17th Dec 2005 at 4:05pm
239             use Util::SelfDestruct('unlink',
240             after => '2005-12-01',
241             before => '2005-12-17 16:05:00',
242             );
243              
244             =head1 DESCRIPTION
245              
246             This module will prevent execution of your script by either dying or
247             deleting (unlinking) the script from disk after it is executed. This
248             can be useful if you have written a script for somebody that must
249             never be executed more than once. A database upgrade script for example.
250              
251             The 'self destruct' mechanism can be achieved through deleting the
252             script so that it cannot be executed again, or by dying (terminating
253             the scripts execution).
254              
255             =head2 Die Method (default)
256              
257             This is the default, and safest behaviour. This allows the script to be
258             executed once. If it is executed again, it will immediately die during the
259             initial compilation phase, preventing the script from fully executing.
260              
261             To do this, the Util::SelfDestruct needs to know if the calling
262             script has ever been executed before. It does this by writing a memo
263             to a file called C<.selfdestruct> in the user's home directory whenever
264             the script is executed. It can therefore find out if the script has
265             been run before during subsequent invocations.
266              
267             =head2 Unlink Method
268              
269             This method should be used with caution. To specify the unlink method,
270             add the C boolean flag as an import paramter (see examples in
271             the synopsis above). Aliases for the C flag are C and
272             C.
273              
274             This method will allow the script to execute, but then delete the file
275             during the cleanup phase after execution. (Specifically during the
276             execution of the END{} in the Util::SelfDestruct module).
277              
278             =head2 Before & After Qualifiers
279              
280             The default behavior of Util::SelfDestruct is to only allow a script to
281             execute once, through either deletion of the script itself, or by dying
282             on all subsqeuent invocations after it's first execution.
283              
284             Instead of this default behaviour, the C and C options allow
285             conditional timing of when the script will self destruct. Specifying
286             C will cause the script to self destruct if executed before the
287             specified date and time. Likewise, the C option will cause the
288             script to self destruct if executed after the specified date. They can also
289             be used in conjunction with eachother to specify a finite time frame.
290              
291             Examples of valid date time formats are as follows:
292              
293             YYYYMMDDHHMMSS
294             YYYYMMDD
295            
296             YYYY-MM-DD HH:MM:SS
297             YYYY-MM-DD
298              
299             Any non-numeric characters will be removed from the date time string before
300             it is parsed. This allows more pleasing formatting to be used.
301              
302             If only a date is specified and not a time, 00:00:00 is assumed in the case
303             of the C option, and 23:59:59 is assumes in the case of the C
304             option.
305              
306             =head1 VERSION
307              
308             $Id: SelfDestruct.pm,v 1.20 2006/01/12 22:45:11 nicolaw Exp $
309              
310             =head1 AUTHOR
311              
312             Nicola Worthington
313              
314             L
315              
316             =head1 COPYRIGHT
317              
318             Copyright 2005,2006 Nicola Worthington.
319              
320             This software is licensed under The Apache Software License, Version 2.0.
321              
322             L
323              
324             =cut
325              
326             __END__