home *** CD-ROM | disk | FTP | other *** search
/ PC Professionell 2004 December / PCpro_2004_12.ISO / files / webserver / tsw / TSW_3.4.0.exe / Apache2 / perl / FileBackend.pm < prev    next >
Encoding:
Perl POD Document  |  2003-04-15  |  13.6 KB  |  755 lines

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