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   13 use strict;
  2         3  
  2         86  
14 2     2   11 use Cache::CacheUtils qw( Assert_Defined Build_Path Freeze_Data Thaw_Data );
  2         3  
  2         128  
15 2     2   1837 use Digest::SHA1 qw( sha1_hex );
  2         2265  
  2         135  
16 2     2   15 use Error;
  2         4  
  2         16  
17 2     2   109 use File::Path qw( mkpath );
  2         5  
  2         143  
18 2     2   2880 use File::Temp qw( tempfile );
  2         55706  
  2         7783  
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 226 my ( $proto, $p_root, $p_depth, $p_directory_umask ) = @_;
37 70   33     319 my $class = ref( $proto ) || $proto;
38 70         226 my $self = {};
39 70         268 $self = bless( $self, $class );
40 70         203 $self->set_root( $p_root );
41 70         174 $self->set_depth( $p_depth );
42 70         198 $self->set_directory_umask( $p_directory_umask );
43 70         317 return $self;
44             }
45              
46              
47             sub delete_key
48             {
49 27     27 0 52 my ( $self, $p_namespace, $p_key ) = @_;
50              
51 27         89 Assert_Defined( $p_namespace );
52 27         73 Assert_Defined( $p_key );
53              
54 27         73 _Remove_File( $self->_path_to_key( $p_namespace, $p_key ) );
55             }
56              
57              
58             sub delete_namespace
59             {
60 27     27 0 57 my ( $self, $p_namespace ) = @_;
61              
62 27         83 Assert_Defined( $p_namespace );
63              
64 27         72 _Recursively_Remove_Directory( Build_Path( $self->get_root( ),
65             $p_namespace ) );
66             }
67              
68              
69             sub get_keys
70             {
71 47     47 0 87 my ( $self, $p_namespace ) = @_;
72              
73 47         152 Assert_Defined( $p_namespace );
74              
75 47         65 my @keys;
76              
77 47         163 foreach my $unique_key ( $self->_get_unique_keys( $p_namespace ) )
78             {
79 60 50       453 my $key = $self->_get_key_for_unique_key( $p_namespace, $unique_key ) or
80             next;
81              
82 60         381 push( @keys, $key );
83             }
84              
85 47         248 return @keys;
86              
87             }
88              
89              
90             sub get_namespaces
91             {
92 36     36 0 66 my ( $self ) = @_;
93              
94 36         51 my @namespaces;
95              
96 36         109 _List_Subdirectories( $self->get_root( ), \@namespaces );
97              
98 36         172 return @namespaces;
99             }
100              
101              
102             sub get_size
103             {
104 119     119 0 241 my ( $self, $p_namespace, $p_key ) = @_;
105              
106 119         395 Assert_Defined( $p_namespace );
107 119         281 Assert_Defined( $p_key );
108              
109 119 50       400 if ( -e $self->_path_to_key( $p_namespace, $p_key ) )
110             {
111 119         325 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 122     122 0 213 my ( $self, $p_namespace, $p_key ) = @_;
124              
125 122         356 Assert_Defined( $p_namespace );
126 122         290 Assert_Defined( $p_key );
127              
128 122         338 return $self->_read_data( $self->_path_to_key($p_namespace, $p_key) )->[1];
129             }
130              
131              
132             sub store
133             {
134 90     90 0 183 my ( $self, $p_namespace, $p_key, $p_data ) = @_;
135              
136 90         235 Assert_Defined( $p_namespace );
137 90         208 Assert_Defined( $p_key );
138              
139 90         279 $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 537     537 0 704 my ( $self ) = @_;
148              
149 537         1582 return $self->{_Depth};
150             }
151              
152              
153             sub set_depth
154             {
155 70     70 0 195 my ( $self, $depth ) = @_;
156              
157 70         206 $self->{_Depth} = $depth;
158             }
159              
160              
161             sub get_root
162             {
163 647     647 0 1155 my ( $self ) = @_;
164              
165 647         2190 return $self->{_Root};
166             }
167              
168              
169             sub set_root
170             {
171 70     70 0 117 my ( $self, $root ) = @_;
172              
173 70         208 $self->{_Root} = $root;
174             }
175              
176              
177             sub get_directory_umask
178             {
179 90     90 0 143 my ( $self ) = @_;
180              
181 90         303 return $self->{_Directory_Umask};
182             }
183              
184              
185             sub set_directory_umask
186             {
187 70     70 0 104 my ( $self, $directory_umask ) = @_;
188              
189 70         168 $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 477     477   634 my ( $p_key ) = @_;
198              
199 477         1147 Assert_Defined( $p_key );
200              
201 477         3039 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   290 my ( $p_directory, $p_optional_new_umask ) = @_;
211              
212 47         152 Assert_Defined( $p_directory );
213              
214 47 50       236 my $old_umask = umask( ) if defined $p_optional_new_umask;
215              
216 47 50       195 umask( $p_optional_new_umask ) if defined $p_optional_new_umask;
217              
218 47         97 my $directory = _Untaint_Path( $p_directory );
219              
220 47         220 $directory =~ s|/$||;
221              
222 47         107539 mkpath( $directory, 0, $DIRECTORY_MODE );
223              
224 47 50       7944 -d $directory or
225             throw Error::Simple( "Couldn't create directory: $directory: $!" );
226              
227 47 50       394 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   62 my ( $p_directory, $p_subdirectories_ref ) = @_;
238              
239 36         103 foreach my $dirent ( _Read_Dirents( $p_directory ) )
240             {
241 108 100 100     618 next if $dirent eq '.' or $dirent eq '..';
242              
243 36         197 my $path = Build_Path( $p_directory, $dirent );
244              
245 36 50       762 next unless -d $path;
246              
247 36         95 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   657 my ( $p_directory ) = @_;
257              
258 443         1166 Assert_Defined( $p_directory );
259              
260 443 50       10536 -d $p_directory or
261             return ( );
262              
263 443         1282 local *Dir;
264              
265 443 50       1062 opendir( Dir, _Untaint_Path( $p_directory ) ) or
266             throw Error::Simple( "Couldn't open directory $p_directory: $!" );
267              
268 443         8688 my @dirents = readdir( Dir );
269              
270 443 50       5212 closedir( Dir ) or
271             throw Error::Simple( "Couldn't close directory $p_directory: $!" );
272              
273 443         2353 return @dirents;
274             }
275              
276              
277             # read in a file. returns a reference to the data read
278              
279             sub _Read_File
280             {
281 151     151   201 my ( $p_path ) = @_;
282              
283 151         401 Assert_Defined( $p_path );
284              
285 151         380 local *File;
286              
287 151 50       291 open( File, _Untaint_Path( $p_path ) ) or
288             return undef;
289              
290 151         406 binmode( File );
291              
292 151         639 local $/ = undef;
293              
294 151         196 my $data_ref;
295              
296 151         3258 $$data_ref = ;
297              
298 151         1833 close( File );
299              
300 151         810 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 182     182   233 my ( $p_path ) = @_;
310              
311 182         410 Assert_Defined( $p_path );
312              
313 182 100       5533 -e $p_path or
314             return undef;
315              
316 151         369 my ( $file_access_time, $file_modified_time ) =
317             ( stat( _Untaint_Path( $p_path ) ) )[8,9];
318              
319 151         466 my $data_ref = _Read_File( $p_path );
320              
321 151         371 utime( $file_access_time, $file_modified_time, _Untaint_Path( $p_path ) );
322              
323 151         776 return $data_ref;
324             }
325              
326              
327             # remove a file
328              
329             sub _Remove_File
330             {
331 61     61   99 my ( $p_path ) = @_;
332              
333 61         169 Assert_Defined( $p_path );
334              
335 61 50       137 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 61         312 unlink( _Untaint_Path( $p_path ) );
341             }
342             }
343              
344              
345             # remove a directory
346              
347             sub _Remove_Directory
348             {
349 166     166   213 my ( $p_directory ) = @_;
350              
351 166         355 Assert_Defined( $p_directory );
352              
353 166 50       473 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         303 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 251     251   452 my ( $p_directory, $p_files_ref ) = @_;
368              
369 251 100       6437 return unless -d $p_directory;
370              
371 241         1303 foreach my $dirent ( _Read_Dirents( $p_directory ) )
372             {
373 746 100 100     4622 next if $dirent eq '.' or $dirent eq '..';
374              
375 264         1169 my $path = Build_Path( $p_directory, $dirent );
376              
377 264 100       8585 if ( -d $path )
378             {
379 204         503 _Recursively_List_Files( $path, $p_files_ref );
380             }
381             else
382             {
383 60         175 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   247 my ( $p_root ) = @_;
419              
420 167 100       3700 return unless -d $p_root;
421              
422 166         495 foreach my $dirent ( _Read_Dirents( $p_root ) )
423             {
424 506 100 100     2334 next if $dirent eq '.' or $dirent eq '..';
425              
426 174         532 my $path = Build_Path( $p_root, $dirent );
427              
428 174 100       4213 if ( -d $path )
429             {
430 140         307 _Recursively_Remove_Directory( $path );
431             }
432             else
433             {
434 34         89 _Remove_File( _Untaint_Path( $path ) );
435             }
436             }
437              
438 166         576 _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 1777     1777   2450 my ( $p_path ) = @_;
481              
482 1777         4630 return _Untaint_String( $p_path, $UNTAINTED_PATH_REGEX );
483             }
484              
485              
486             # Untaint a string
487              
488             sub _Untaint_String
489             {
490 1777     1777   3023 my ( $p_string, $p_untainted_regex ) = @_;
491              
492 1777         4622 Assert_Defined( $p_string );
493 1777         4269 Assert_Defined( $p_untainted_regex );
494              
495 1777         8651 my ( $untainted_string ) = $p_string =~ /$p_untainted_regex/;
496              
497 1777 50 33     5025 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 1777         272851 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 90     90   132 my ( $p_path, $p_optional_new_umask ) = @_;
512              
513 90         6514 my ( $volume, $directory, $filename ) = File::Spec->splitpath( $p_path );
514              
515 90 50 33     495 if ( defined $directory and defined $volume )
516             {
517 90         794 $directory = File::Spec->catpath( $volume, $directory, "" );
518             }
519              
520 90 100 66     3638 if ( defined $directory and not -d $directory )
521             {
522 47         142 _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 537     537   767 my ( $p_word, $p_depth ) = @_;
532              
533 537         1166 Assert_Defined( $p_word );
534 537         1492 Assert_Defined( $p_depth );
535              
536 537         662 my @split_word_list;
537              
538 537         1891 for ( my $i = 0; $i < $p_depth; $i++ )
539             {
540 1611         5467 push ( @split_word_list, substr( $p_word, $i, 1 ) );
541             }
542              
543 537         2805 return @split_word_list;
544             }
545              
546              
547             # write a file atomically
548              
549             sub _Write_File
550             {
551 90     90   174 my ( $p_path, $p_data_ref, $p_optional_mode, $p_optional_umask ) = @_;
552              
553 90         261 Assert_Defined( $p_path );
554 90         290 Assert_Defined( $p_data_ref );
555              
556 90 50       226 my $old_umask = umask if $p_optional_umask;
557              
558 90 50       275 umask( $p_optional_umask ) if $p_optional_umask;
559              
560 90         1530 my ( $volume, $directory, $filename ) = File::Spec->splitpath( $p_path );
561            
562 90 50 33     503 if ( defined $directory and defined $volume )
563             {
564 90         784 $directory = File::Spec->catpath( $volume, $directory, "" );
565             }
566              
567 90         467 my ( $temp_fh, $temp_filename ) = tempfile( DIR => $directory );
568              
569 90         51982 binmode( $temp_fh );
570              
571 90         507 print $temp_fh $$p_data_ref;
572              
573 90         4709 close( $temp_fh );
574              
575 90 50       3085 -e $temp_filename or
576             throw Error::Simple( "Temp file '$temp_filename' does not exist: $!" );
577            
578 90 50       263 rename( $temp_filename, _Untaint_Path( $p_path ) ) or
579             throw Error::Simple( "Couldn't rename $temp_filename to $p_path: $!" );
580              
581 90 50       2442 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 90   33     1388 $p_optional_mode ||= 0666 - umask( );
588              
589 90         487 chmod( $p_optional_mode, _Untaint_Path($p_path) );
590              
591 90 50       1323 umask( $old_umask ) if $old_umask;
592             }
593              
594              
595             sub _get_key_for_unique_key
596             {
597 60     60   137 my ( $self, $p_namespace, $p_unique_key ) = @_;
598              
599 60         168 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 47     47   97 my ( $self, $p_namespace ) = @_;
607              
608 47         170 Assert_Defined( $p_namespace );
609              
610 47         70 my @unique_keys;
611              
612 47         172 _Recursively_List_Files( Build_Path( $self->get_root( ), $p_namespace ),
613             \@unique_keys );
614              
615 47         297 return @unique_keys;
616             }
617              
618              
619             sub _path_to_key
620             {
621 477     477   2410 my ( $self, $p_namespace, $p_key ) = @_;
622              
623 477         1397 Assert_Defined( $p_namespace );
624 477         1063 Assert_Defined( $p_key );
625              
626 477         928 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 537     537   897 my ( $self, $p_namespace, $p_unique_key ) = @_;
634              
635 537         1307 Assert_Defined( $p_unique_key );
636 537         1534 Assert_Defined( $p_namespace );
637              
638 537         1220 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 182     182   377 my ( $self, $p_path ) = @_;
649              
650 182         469 Assert_Defined( $p_path );
651              
652 182 100       572 my $frozen_data_ref = _Read_File_Without_Time_Modification( $p_path ) or
653             return [ undef, undef ];
654              
655 151         273 my $data_ref = eval{ Thaw_Data( $$frozen_data_ref ) };
  151         645  
656            
657 151 50 33     5416 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 151         1017 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 90     90   319 my ( $self, $p_path, $p_data ) = @_;
674              
675 90         250 Assert_Defined( $p_path );
676 90         220 Assert_Defined( $p_data );
677              
678 90         258 _Make_Path( $p_path, $self->get_directory_umask( ) );
679              
680 90         398 my $frozen_file = Freeze_Data( $p_data );
681              
682 90         7491 _Write_File( $p_path, \$frozen_file );
683             }
684              
685              
686             1;
687              
688              
689             __END__