File Coverage

blib/lib/Cache/FileBackend.pm
Criterion Covered Total %
statement 204 228 89.4
branch 43 76 56.5
condition 17 36 47.2
subroutine 41 43 95.3
pod 1 14 7.1
total 306 397 77.0


line stmt bran cond sub pod time code
1             ######################################################################
2             # $Id: FileBackend.pm,v 1.27 2005/03/17 19:31:27 dclinton Exp $
3             # Copyright (C) 2001-2003 DeWitt Clinton All Rights Reserved
4             #
5             # Software distributed under the License is distributed on an "AS
6             # IS" basis, WITHOUT WARRANTY OF ANY KIND, either expressed or
7             # implied. See the License for the specific language governing
8             # rights and limitations under the License.
9             ######################################################################
10              
11             package Cache::FileBackend;
12              
13 2     2   12 use strict;
  2         3  
  2         102  
14 2     2   10 use Cache::CacheUtils qw( Assert_Defined Build_Path Freeze_Data Thaw_Data );
  2         2  
  2         107  
15 2     2   1026 use Digest::SHA1 qw( sha1_hex );
  2         1752  
  2         149  
16 2     2   18 use Error;
  2         3  
  2         16  
17 2     2   114 use File::Path qw( mkpath );
  2         4  
  2         226  
18 2     2   1828 use File::Temp qw( tempfile );
  2         40869  
  2         4950  
19              
20              
21             # the file mode for new directories, which will be modified by the
22             # current umask
23              
24             my $DIRECTORY_MODE = 0777;
25              
26              
27             # regex for untainting directory and file paths. since all paths are
28             # generated by us or come from user via API, a tautological regex
29             # suffices.
30              
31             my $UNTAINTED_PATH_REGEX = '^(.*)$';
32              
33              
34             sub new
35             {
36 70     70 1 118 my ( $proto, $p_root, $p_depth, $p_directory_umask ) = @_;
37 70   33     259 my $class = ref( $proto ) || $proto;
38 70         90 my $self = {};
39 70         131 $self = bless( $self, $class );
40 70         165 $self->set_root( $p_root );
41 70         138 $self->set_depth( $p_depth );
42 70         143 $self->set_directory_umask( $p_directory_umask );
43 70         243 return $self;
44             }
45              
46              
47             sub delete_key
48             {
49 28     28 0 47 my ( $self, $p_namespace, $p_key ) = @_;
50              
51 28         69 Assert_Defined( $p_namespace );
52 28         64 Assert_Defined( $p_key );
53              
54 28         64 _Remove_File( $self->_path_to_key( $p_namespace, $p_key ) );
55             }
56              
57              
58             sub delete_namespace
59             {
60 27     27 0 50 my ( $self, $p_namespace ) = @_;
61              
62 27         72 Assert_Defined( $p_namespace );
63              
64 27         60 _Recursively_Remove_Directory( Build_Path( $self->get_root( ),
65             $p_namespace ) );
66             }
67              
68              
69             sub get_keys
70             {
71 48     48 0 68 my ( $self, $p_namespace ) = @_;
72              
73 48         205 Assert_Defined( $p_namespace );
74              
75 48         53 my @keys;
76              
77 48         150 foreach my $unique_key ( $self->_get_unique_keys( $p_namespace ) )
78             {
79 60 50       182 my $key = $self->_get_key_for_unique_key( $p_namespace, $unique_key ) or
80             next;
81              
82 60         338 push( @keys, $key );
83             }
84              
85 48         198 return @keys;
86              
87             }
88              
89              
90             sub get_namespaces
91             {
92 36     36 0 49 my ( $self ) = @_;
93              
94 36         38 my @namespaces;
95              
96 36         73 _List_Subdirectories( $self->get_root( ), \@namespaces );
97              
98 36         141 return @namespaces;
99             }
100              
101              
102             sub get_size
103             {
104 121     121 0 184 my ( $self, $p_namespace, $p_key ) = @_;
105              
106 121         270 Assert_Defined( $p_namespace );
107 121         290 Assert_Defined( $p_key );
108              
109 121 50       259 if ( -e $self->_path_to_key( $p_namespace, $p_key ) )
110             {
111 121         295 return -s $self->_path_to_key( $p_namespace, $p_key );
112              
113             }
114             else
115             {
116 0         0 return 0;
117             }
118             }
119              
120              
121             sub restore
122             {
123 124     124 0 177 my ( $self, $p_namespace, $p_key ) = @_;
124              
125 124         249 Assert_Defined( $p_namespace );
126 124         236 Assert_Defined( $p_key );
127              
128 124         395 return $self->_read_data( $self->_path_to_key($p_namespace, $p_key) )->[1];
129             }
130              
131              
132             sub store
133             {
134 92     92 0 166 my ( $self, $p_namespace, $p_key, $p_data ) = @_;
135              
136 92         226 Assert_Defined( $p_namespace );
137 92         166 Assert_Defined( $p_key );
138              
139 92         283 $self->_write_data( $self->_path_to_key( $p_namespace, $p_key ),
140             [ $p_key, $p_data ] );
141              
142             }
143              
144              
145             sub get_depth
146             {
147 546     546 0 518 my ( $self ) = @_;
148              
149 546         1153 return $self->{_Depth};
150             }
151              
152              
153             sub set_depth
154             {
155 70     70 0 87 my ( $self, $depth ) = @_;
156              
157 70         114 $self->{_Depth} = $depth;
158             }
159              
160              
161             sub get_root
162             {
163 657     657 0 726 my ( $self ) = @_;
164              
165 657         1786 return $self->{_Root};
166             }
167              
168              
169             sub set_root
170             {
171 70     70 0 93 my ( $self, $root ) = @_;
172              
173 70         162 $self->{_Root} = $root;
174             }
175              
176              
177             sub get_directory_umask
178             {
179 92     92 0 122 my ( $self ) = @_;
180              
181 92         302 return $self->{_Directory_Umask};
182             }
183              
184              
185             sub set_directory_umask
186             {
187 70     70 0 80 my ( $self, $directory_umask ) = @_;
188              
189 70         208 $self->{_Directory_Umask} = $directory_umask;
190             }
191              
192              
193             # Take an human readable key, and create a unique key from it
194              
195             sub _Build_Unique_Key
196             {
197 486     486   570 my ( $p_key ) = @_;
198              
199 486         827 Assert_Defined( $p_key );
200              
201 486         2661 return sha1_hex( $p_key );
202             }
203              
204              
205             # create a directory with optional mask, building subdirectories as
206             # needed.
207              
208             sub _Create_Directory
209             {
210 47     47   78 my ( $p_directory, $p_optional_new_umask ) = @_;
211              
212 47         125 Assert_Defined( $p_directory );
213              
214 47 50       151 my $old_umask = umask( ) if defined $p_optional_new_umask;
215              
216 47 50       112 umask( $p_optional_new_umask ) if defined $p_optional_new_umask;
217              
218 47         82 my $directory = _Untaint_Path( $p_directory );
219              
220 47         203 $directory =~ s|/$||;
221              
222 47         19834 mkpath( $directory, 0, $DIRECTORY_MODE );
223              
224 47 50       789 -d $directory or
225             throw Error::Simple( "Couldn't create directory: $directory: $!" );
226              
227 47 50       199 umask( $old_umask ) if defined $old_umask;
228             }
229              
230              
231              
232             # list the names of the subdirectories in a given directory, without the
233             # full path
234              
235             sub _List_Subdirectories
236             {
237 36     36   49 my ( $p_directory, $p_subdirectories_ref ) = @_;
238              
239 36         76 foreach my $dirent ( _Read_Dirents( $p_directory ) )
240             {
241 108 100 100     474 next if $dirent eq '.' or $dirent eq '..';
242              
243 36         119 my $path = Build_Path( $p_directory, $dirent );
244              
245 36 50       586 next unless -d $path;
246              
247 36         93 push( @$p_subdirectories_ref, $dirent );
248             }
249             }
250              
251              
252             # read the dirents from a directory
253              
254             sub _Read_Dirents
255             {
256 443     443   500 my ( $p_directory ) = @_;
257              
258 443         982 Assert_Defined( $p_directory );
259              
260 443 50       5713 -d $p_directory or
261             return ( );
262              
263 443         1104 local *Dir;
264              
265 443 50       799 opendir( Dir, _Untaint_Path( $p_directory ) ) or
266             throw Error::Simple( "Couldn't open directory $p_directory: $!" );
267              
268 443         6113 my @dirents = readdir( Dir );
269              
270 443 50       3260 closedir( Dir ) or
271             throw Error::Simple( "Couldn't close directory $p_directory: $!" );
272              
273 443         2051 return @dirents;
274             }
275              
276              
277             # read in a file. returns a reference to the data read
278              
279             sub _Read_File
280             {
281 153     153   248 my ( $p_path ) = @_;
282              
283 153         340 Assert_Defined( $p_path );
284              
285 153         402 local *File;
286              
287 153 50       282 open( File, _Untaint_Path( $p_path ) ) or
288             return undef;
289              
290 153         406 binmode( File );
291              
292 153         681 local $/ = undef;
293              
294 153         147 my $data_ref;
295              
296 153         2309 $$data_ref = ;
297              
298 153         1282 close( File );
299              
300 153         784 return $data_ref;
301             }
302              
303              
304             # read in a file. returns a reference to the data read, without
305             # modifying the last accessed time
306              
307             sub _Read_File_Without_Time_Modification
308             {
309 184     184   216 my ( $p_path ) = @_;
310              
311 184         343 Assert_Defined( $p_path );
312              
313 184 100       4575 -e $p_path or
314             return undef;
315              
316 153         369 my ( $file_access_time, $file_modified_time ) =
317             ( stat( _Untaint_Path( $p_path ) ) )[8,9];
318              
319 153         478 my $data_ref = _Read_File( $p_path );
320              
321 153         364 utime( $file_access_time, $file_modified_time, _Untaint_Path( $p_path ) );
322              
323 153         650 return $data_ref;
324             }
325              
326              
327             # remove a file
328              
329             sub _Remove_File
330             {
331 62     62   428 my ( $p_path ) = @_;
332              
333 62         133 Assert_Defined( $p_path );
334              
335 62 50       124 if ( -f _Untaint_Path( $p_path ) )
336             {
337             # We don't catch the error, because this may fail if two
338             # processes are in a race and try to remove the object
339              
340 62         137 unlink( _Untaint_Path( $p_path ) );
341             }
342             }
343              
344              
345             # remove a directory
346              
347             sub _Remove_Directory
348             {
349 166     166   236 my ( $p_directory ) = @_;
350              
351 166         299 Assert_Defined( $p_directory );
352              
353 166 50       222 if ( -d _Untaint_Path( $p_directory ) )
354             {
355             # We don't catch the error, because this may fail if two
356             # processes are in a race and try to remove the object
357              
358 166         540 rmdir( _Untaint_Path( $p_directory ) );
359             }
360             }
361              
362              
363             # recursively list the files of the subdirectories, without the full paths
364              
365             sub _Recursively_List_Files
366             {
367 252     252   338 my ( $p_directory, $p_files_ref ) = @_;
368              
369 252 100       3108 return unless -d $p_directory;
370              
371 241         441 foreach my $dirent ( _Read_Dirents( $p_directory ) )
372             {
373 746 100 100     2661 next if $dirent eq '.' or $dirent eq '..';
374              
375 264         687 my $path = Build_Path( $p_directory, $dirent );
376              
377 264 100       4541 if ( -d $path )
378             {
379 204         394 _Recursively_List_Files( $path, $p_files_ref );
380             }
381             else
382             {
383 60         176 push( @$p_files_ref, $dirent );
384             }
385             }
386             }
387              
388              
389             # recursively list the files of the subdirectories, with the full paths
390              
391             sub _Recursively_List_Files_With_Paths
392             {
393 0     0   0 my ( $p_directory, $p_files_ref ) = @_;
394              
395 0         0 foreach my $dirent ( _Read_Dirents( $p_directory ) )
396             {
397 0 0 0     0 next if $dirent eq '.' or $dirent eq '..';
398              
399 0         0 my $path = Build_Path( $p_directory, $dirent );
400              
401 0 0       0 if ( -d $path )
402             {
403 0         0 _Recursively_List_Files_With_Paths( $path, $p_files_ref );
404             }
405             else
406             {
407 0         0 push( @$p_files_ref, $path );
408             }
409             }
410             }
411              
412              
413              
414             # remove a directory and all subdirectories and files
415              
416             sub _Recursively_Remove_Directory
417             {
418 167     167   205 my ( $p_root ) = @_;
419              
420 167 100       2540 return unless -d $p_root;
421              
422 166         309 foreach my $dirent ( _Read_Dirents( $p_root ) )
423             {
424 506 100 100     2181 next if $dirent eq '.' or $dirent eq '..';
425              
426 174         439 my $path = Build_Path( $p_root, $dirent );
427              
428 174 100       2875 if ( -d $path )
429             {
430 140         306 _Recursively_Remove_Directory( $path );
431             }
432             else
433             {
434 34         78 _Remove_File( _Untaint_Path( $path ) );
435             }
436             }
437              
438 166         386 _Remove_Directory( _Untaint_Path( $p_root ) );
439             }
440              
441              
442              
443             # walk down a directory structure and total the size of the files
444             # contained therein.
445              
446             sub _Recursive_Directory_Size
447             {
448 0     0   0 my ( $p_directory ) = @_;
449              
450 0         0 Assert_Defined( $p_directory );
451              
452 0 0       0 return 0 unless -d $p_directory;
453              
454 0         0 my $size = 0;
455              
456 0         0 foreach my $dirent ( _Read_Dirents( $p_directory ) )
457             {
458 0 0 0     0 next if $dirent eq '.' or $dirent eq '..';
459              
460 0         0 my $path = Build_Path( $p_directory, $dirent );
461              
462 0 0       0 if ( -d $path )
463             {
464 0         0 $size += _Recursive_Directory_Size( $path );
465             }
466             else
467             {
468 0         0 $size += -s $path;
469             }
470             }
471              
472 0         0 return $size;
473             }
474              
475              
476             # Untaint a file path
477              
478             sub _Untaint_Path
479             {
480 1789     1789   2265 my ( $p_path ) = @_;
481              
482 1789         2842 return _Untaint_String( $p_path, $UNTAINTED_PATH_REGEX );
483             }
484              
485              
486             # Untaint a string
487              
488             sub _Untaint_String
489             {
490 1789     1789   1974 my ( $p_string, $p_untainted_regex ) = @_;
491              
492 1789         3464 Assert_Defined( $p_string );
493 1789         3006 Assert_Defined( $p_untainted_regex );
494              
495 1789         9404 my ( $untainted_string ) = $p_string =~ /$p_untainted_regex/;
496              
497 1789 50 33     4045 if ( not defined $untainted_string || $untainted_string ne $p_string )
498             {
499 0         0 throw Error::Simple( "String $p_string contains possible taint" );
500             }
501              
502 1789         254198 return $untainted_string;
503             }
504              
505              
506             # create a directory with the optional umask if it doesn't already
507             # exist
508              
509             sub _Make_Path
510             {
511 92     92   128 my ( $p_path, $p_optional_new_umask ) = @_;
512              
513 92         1616 my ( $volume, $directory, $filename ) = File::Spec->splitpath( $p_path );
514              
515 92 50 33     457 if ( defined $directory and defined $volume )
516             {
517 92         607 $directory = File::Spec->catpath( $volume, $directory, "" );
518             }
519              
520 92 100 66     2309 if ( defined $directory and not -d $directory )
521             {
522 47         137 _Create_Directory( $directory, $p_optional_new_umask );
523             }
524             }
525              
526              
527             # return a list of the first $depth letters in the $word
528              
529             sub _Split_Word
530             {
531 546     546   574 my ( $p_word, $p_depth ) = @_;
532              
533 546         949 Assert_Defined( $p_word );
534 546         927 Assert_Defined( $p_depth );
535              
536 546         472 my @split_word_list;
537              
538 546         1558 for ( my $i = 0; $i < $p_depth; $i++ )
539             {
540 1638         3959 push ( @split_word_list, substr( $p_word, $i, 1 ) );
541             }
542              
543 546         2130 return @split_word_list;
544             }
545              
546              
547             # write a file atomically
548              
549             sub _Write_File
550             {
551 92     92   187 my ( $p_path, $p_data_ref, $p_optional_mode, $p_optional_umask ) = @_;
552              
553 92         213 Assert_Defined( $p_path );
554 92         176 Assert_Defined( $p_data_ref );
555              
556 92 50       178 my $old_umask = umask if $p_optional_umask;
557              
558 92 50       189 umask( $p_optional_umask ) if $p_optional_umask;
559              
560 92         1218 my ( $volume, $directory, $filename ) = File::Spec->splitpath( $p_path );
561            
562 92 50 33     536 if ( defined $directory and defined $volume )
563             {
564 92         724 $directory = File::Spec->catpath( $volume, $directory, "" );
565             }
566              
567 92         437 my ( $temp_fh, $temp_filename ) = tempfile( DIR => $directory );
568              
569 92         30859 binmode( $temp_fh );
570              
571 92         461 print $temp_fh $$p_data_ref;
572              
573 92         46979 close( $temp_fh );
574              
575 92 50       1822 -e $temp_filename or
576             throw Error::Simple( "Temp file '$temp_filename' does not exist: $!" );
577            
578 92 50       277 rename( $temp_filename, _Untaint_Path( $p_path ) ) or
579             throw Error::Simple( "Couldn't rename $temp_filename to $p_path: $!" );
580              
581 92 50       1802 if ( -e $temp_filename )
582             {
583 0         0 _Remove_File( $temp_filename );
584 0         0 warn( "Temp file '$temp_filename' shouldn't still exist" );
585             }
586              
587 92   33     528 $p_optional_mode ||= 0666 - umask( );
588              
589 92         209 chmod( $p_optional_mode, _Untaint_Path($p_path) );
590              
591 92 50       1050 umask( $old_umask ) if $old_umask;
592             }
593              
594              
595             sub _get_key_for_unique_key
596             {
597 60     60   96 my ( $self, $p_namespace, $p_unique_key ) = @_;
598              
599 60         142 return $self->_read_data( $self->_path_to_unique_key( $p_namespace,
600             $p_unique_key ) )->[0];
601             }
602              
603              
604             sub _get_unique_keys
605             {
606 48     48   65 my ( $self, $p_namespace ) = @_;
607              
608 48         111 Assert_Defined( $p_namespace );
609              
610 48         46 my @unique_keys;
611              
612 48         110 _Recursively_List_Files( Build_Path( $self->get_root( ), $p_namespace ),
613             \@unique_keys );
614              
615 48         204 return @unique_keys;
616             }
617              
618              
619             sub _path_to_key
620             {
621 486     486   636 my ( $self, $p_namespace, $p_key ) = @_;
622              
623 486         865 Assert_Defined( $p_namespace );
624 486         842 Assert_Defined( $p_key );
625              
626 486         818 return $self->_path_to_unique_key( $p_namespace,
627             _Build_Unique_Key( $p_key ) );
628             }
629              
630              
631             sub _path_to_unique_key
632             {
633 546     546   759 my ( $self, $p_namespace, $p_unique_key ) = @_;
634              
635 546         926 Assert_Defined( $p_unique_key );
636 546         830 Assert_Defined( $p_namespace );
637              
638 546         1103 return Build_Path( $self->get_root( ),
639             $p_namespace,
640             _Split_Word( $p_unique_key, $self->get_depth( ) ),
641             $p_unique_key );
642             }
643              
644             # the data is returned as reference to an array ( key, data )
645              
646             sub _read_data
647             {
648 184     184   284 my ( $self, $p_path ) = @_;
649              
650 184         371 Assert_Defined( $p_path );
651              
652 184 100       396 my $frozen_data_ref = _Read_File_Without_Time_Modification( $p_path ) or
653             return [ undef, undef ];
654              
655 153         282 my $data_ref = eval{ Thaw_Data( $$frozen_data_ref ) };
  153         539  
656            
657 153 50 33     4724 if ( $@ || ( ref( $data_ref ) ne 'ARRAY' ) )
658             {
659 0         0 unlink _Untaint_Path( $p_path );
660 0         0 return [ undef, undef ];
661             }
662             else
663             {
664 153         894 return $data_ref;
665             }
666             }
667              
668              
669             # the data is passed as reference to an array ( key, data )
670              
671             sub _write_data
672             {
673 92     92   176 my ( $self, $p_path, $p_data ) = @_;
674              
675 92         190 Assert_Defined( $p_path );
676 92         304 Assert_Defined( $p_data );
677              
678 92         246 _Make_Path( $p_path, $self->get_directory_umask( ) );
679              
680 92         349 my $frozen_file = Freeze_Data( $p_data );
681              
682 92         5899 _Write_File( $p_path, \$frozen_file );
683             }
684              
685              
686             1;
687              
688              
689             __END__