File Coverage

blib/lib/Tie/FileSystem.pm
Criterion Covered Total %
statement 24 211 11.3
branch 0 130 0.0
condition 0 3 0.0
subroutine 8 23 34.7
pod 0 6 0.0
total 32 373 8.5


line stmt bran cond sub pod time code
1             #-----------------------------------------------------------------------
2             # Copyright 2003-2007 Vadim V. Kouevda,
3             # "KAITS, Inc." All rights reserved.
4             #-----------------------------------------------------------------------
5             # $Id: FileSystem.pm,v 2.15 2007/03/21 00:11:01 vadim Exp $
6             #-----------------------------------------------------------------------
7             # Authors: Vadim V. Kouevda initdotd@gmail.com
8             #-----------------------------------------------------------------------
9             # Description: This is an interface to the file system as easy as to the
10             # hash. You just need to declare:
11             # my $contents = $dir{'etc'}{'passwd'}
12             # ... and /etc/passwd will be read into the variable $contents.
13             #-----------------------------------------------------------------------
14              
15             package Tie::FileSystem;
16              
17 1     1   30194 use vars qw($VERSION @ISA @EXPORT);
  1         3  
  1         80  
18 1     1   6 use strict; # Makes life miserable
  1         3  
  1         33  
19 1     1   5 use Exporter; # Inheritance
  1         3  
  1         51  
20 1     1   1163 use Tie::Hash; # That's what we do :-)
  1         1398  
  1         30  
21 1     1   1691 use Data::Dumper; # Great debug tool
  1         13681  
  1         78  
22 1     1   1395 use Symbol; # Handler generator
  1         1636  
  1         75  
23 1     1   6 use Fcntl ':mode'; # Better tests "-f" & "-d"
  1         2  
  1         405  
24              
25 1     1   580 use Tie::FileSystem::System;# Subroutines for system files
  1         4  
  1         3868  
26              
27             #-----------------------------------------------------------------------
28              
29             $VERSION = sprintf("%d.%d", q$Revision: 2.15 $ =~ /(\d+)\.(\d+)/);
30             @ISA = qw(Tie::FileSystem::System Tie::Hash Exporter);
31             @EXPORT = qw(); # Everything's private
32              
33             #-----------------------------------------------------------------------
34             # Tunable variables
35             #-----------------------------------------------------------------------
36              
37             my $symbol; # Randomized DIR handler
38              
39             #-----------------------------------------------------------------------
40             # Define file handlers
41             #-----------------------------------------------------------------------
42              
43             my %file_type = (
44             'default' => sub {
45             #---------------------------------------------------------------
46             # By default we just read the file into a string
47             #---------------------------------------------------------------
48             my ($file, $dbg, $size_limit) = @_;
49             if ( ! open(FILE, "$file") ) {
50             $dbg && debug($dbg, "Failed to open file '$file'");
51             return(undef);
52             }
53             my $buffer; # AUX buffer for file reading
54             my $buf_size = 10485760; # Do not read more than 10MB
55             if ( $size_limit ) { $buf_size = $size_limit; }
56             if ($dbg >= 6 ) { debug(6, ['size_limit'], [$size_limit]); }
57             if ($dbg >= 6 ) { debug(6, ['buf_size'], [$buf_size]); }
58             my $bytes = read(FILE, $buffer, $buf_size);
59             if ( $bytes == $buf_size ) {
60             $dbg && debug($dbg, "Buffer limit '$buf_size' reached");
61             return(undef);
62             }
63             close(FILE);
64             return($buffer);
65             },
66             '/etc/passwd$' => \&passwd,
67             );
68              
69             #=======================================================================
70             # Auxiliary "system level" subroutines
71             #=======================================================================
72              
73             my @level = ( 'SILENT', # No output at all, ERRORs are suppressed
74             'ERROR', # ERRORs are printed to STDERR
75             'WARNING', # WARNINGs are printed to STDERR
76             'INFO', # Information messages
77             'D:IN/OUT', # Important variables
78             'D:LOGIC', # Logical desicions
79             'D:VARS', # Variables
80             );
81              
82             #-----------------------------------------------------------------------
83             # Debug output
84             #-----------------------------------------------------------------------
85              
86             sub debug {
87 0     0 0   my $dbg = shift(@_);
88             #-------------------------------------------------------------------
89 0           print STDERR "", (caller(1))[3], " [", $level[$dbg], "] ";
90             #-------------------------------------------------------------------
91 0           $Data::Dumper::Terse = 1;
92 0 0         if ( scalar(@_) == 1 ) { print $_[0]; shift(@_); }
  0            
  0            
93 0 0         if ( scalar(@_) <= 0 ) { print "\n"; return; }
  0            
  0            
94 0 0         if ( scalar(@_) > 2 ) { print "INCORRECT debug USAGE\n"; return; }
  0            
  0            
95 0           foreach my $idx ( 0 .. scalar(@{$_[0]})-1 ) {
  0            
96 0           print STDERR $_[0][$idx], " = ", Dumper($_[1][$idx]);
97             }
98 0           return;
99             }
100              
101             #-----------------------------------------------------------------------
102             # Better determination of the file type
103             #-----------------------------------------------------------------------
104              
105             sub filetype {
106 0     0 0   my ($filename) = @_;
107 0           my @stat = stat($filename);
108 0 0         if ( S_ISDIR($stat[2]) ) { return('DIR'); }
  0            
109 0 0         if ( S_ISREG($stat[2]) ) { return('FILE'); }
  0            
110 0           return(undef);
111             }
112              
113             #=======================================================================
114             # Supported functions, required for tied hash implementation.
115             #=======================================================================
116              
117             sub TIEHASH {
118 0     0     my ( $class, %args ) = @_;
119             #-------------------------------------------------------------------
120 0 0         if ( ! defined($args{'dbg'}) ) { $args{'dbg'} = 0; }
  0            
121 0 0         if ($args{'dbg'} >= 4 ) { debug(4, ['ARGS'], [\%args]); }
  0            
122             #-------------------------------------------------------------------
123             # Verify arguments
124             #-------------------------------------------------------------------
125 0 0         if ( ! defined($args{'dir'}) ) {
126 0 0         $args{'dbg'} && debug(1, "Directory name is required");
127 0           return(undef);
128             }
129 0 0         if ( filetype($args{'dir'}) ne "DIR" ) {
130 0 0         $args{'dbg'} && debug(1, "$args{'dir'} is not a directory");
131 0           return(undef);
132             }
133             #-------------------------------------------------------------------
134 0 0         if ($args{'dbg'} >= 4 ) { debug(4, "OUT"); }
  0            
135 0   0       return ( bless ( [ \%args, # [0] Hash of options
136             undef, # [1] List of elements (quick access)
137             undef, # [2] Index of current element
138             undef ], # [3] Reference to a hashed contents
139             ref($class) || $class
140             )
141             );
142             #-------------------------------------------------------------------
143             }
144              
145             #=======================================================================
146              
147             sub FIRSTKEY {
148 0     0     my ( $this ) = @_;
149 0 0         if ( $this->[0]{'dbg'} >= 4 ) { debug(4, "IN"); }
  0            
150             #-------------------------------------------------------------------
151             # Get on demand contents
152             #-------------------------------------------------------------------
153 0 0         if ( ! $this->[1] ) { dir_contents($this); }
  0            
154 0 0         if ( ! $this->[1] ) {
155 0 0         $this->[0]{'dbg'} && debug(1, "contents is not defined");
156 0           return(undef);
157             }
158             #-------------------------------------------------------------------
159 0 0         if ( ! defined($this->[2]) ) { $this->[2] = 0; }
  0            
160 0           my $idx = $this->[2]++; # Advance iterator to the next element
161 0 0         if ( $this->[0]{'dbg'} >= 4 ) { debug(4, "OUT"); }
  0            
162 0           return($this->[1][$idx]);
163             #-------------------------------------------------------------------
164             }
165              
166             #-----------------------------------------------------------------------
167              
168             sub NEXTKEY {
169 0     0     my ( $this, $last ) = @_;
170 0 0         if ( $this->[0]{'dbg'} >= 4 ) { debug(4, "IN"); }
  0            
171             #-------------------------------------------------------------------
172             # Extract current index and forward by one the stored counter.
173             #-------------------------------------------------------------------
174 0 0         if ( $this->[0]{'dbg'} ) {
175 0           print STDERR (caller(0))[3], " [DEBUG] in\n";
176             }
177             #-------------------------------------------------------------------
178 0           my $idx = $this->[2]++;
179             #-------------------------------------------------------------------
180             # Return next.
181             #-------------------------------------------------------------------
182 0 0         if ( $this->[0]{'dbg'} >= 4 ) { debug(4, "OUT"); }
  0            
183 0 0         if ( scalar @{$this->[1]} > $idx ) {
  0            
184 0           return($this->[1][$idx]);
185             } else {
186 0           return(undef);
187             }
188             #-------------------------------------------------------------------
189             }
190              
191             #-----------------------------------------------------------------------
192              
193             sub EXISTS {
194 0     0     my ( $this, $key ) = @_;
195 0 0         if ( $this->[0]{'dbg'} >= 4 ) { debug(4, ['KEY'], [$key]); }
  0            
196             #-------------------------------------------------------------------
197             # Fetch contents on demand!
198             #-------------------------------------------------------------------
199 0 0         if ( ! $this->[1] ) { dir_contents($this); }
  0            
200 0 0         if ( ! $this->[1] ) {
201 0 0         if ( $this->[0]{'dbg'} >= 4 )
202 0           { debug(4, "does not exist"); }
203 0 0         if ( $this->[0]{'dbg'} >= 5 )
204 0           { debug(5, ['contents'], [$this->[1]]); }
205 0           return(0);
206             }
207 0           my $exists = grep { /^$key$/ } @{$this->[1]};
  0            
  0            
208             #-------------------------------------------------------------------
209 0 0         if ( $exists ) {
210 0 0         if ( $this->[0]{'dbg'} >= 3 ) { debug(3, "exists: '$key'"); }
  0            
211             } else {
212 0 0         if ( $this->[0]{'dbg'} >= 3 ) { debug(3, "not found: '$key'"); }
  0            
213             }
214 0 0         if ( $this->[0]{'dbg'} >= 4 ) { debug(4, "OUT"); }
  0            
215 0           return($exists);
216             #-------------------------------------------------------------------
217             }
218              
219             #-----------------------------------------------------------------------
220              
221             sub FETCH {
222 0     0     my ( $this, $key ) = @_;
223 0 0         if ( $this->[0]{'dbg'} >= 4 ) { debug(4, ['KEY'], [$key]); }
  0            
224             #-------------------------------------------------------------------
225             # Does it exist?
226             #-------------------------------------------------------------------
227 0 0         if ( ! EXISTS( $this, $key ) ) {
228 0 0         if ( $this->[0]{'dbg'} >= 4 ) { debug(4, "OUT"); }
  0            
229 0           return(undef);
230             }
231 0 0         if ( $this->[0]{'dbg'} >= 3 ) {
232 0           debug(3, "get contents for '$key' (" .
233             ref($this->[3]{$key}) . ")");
234             }
235             #-------------------------------------------------------------------
236 0 0         if ( ! defined($this->[3]{$key}) ) { return(undef); } # Unknown
  0            
237             #-------------------------------------------------------------------
238             # Is it directory of a file?
239             #-------------------------------------------------------------------
240 0           (my $entry = "$this->[0]{'dir'}/$key") =~ s{/+}{/}g;
241 0 0         if ( ref($this->[3]{$key}) eq 'HASH' ) { # Directory
242 0 0         if ( $this->[0]{'dbg'} >= 5 ) { debug(5, "This is a dir"); }
  0            
243 0 0         if ( $this->[0]{'dbg'} >= 6 )
244 0           { debug(6, ['KEY', 'ENTRY'], [$key, $entry]); }
245 0           tie %{$this->[3]{$key}}, "Tie::FileSystem",
  0            
246             ( 'dbg' => $this->[0]{'dbg'},
247             'buf_size' => $this->[0]{'buf_size'},
248             'dir' => $entry);
249             } else { # File
250 0 0         if ( $this->[0]{'dbg'} >= 5 ) {debug(5, "This is a file");}
  0            
251 0           $this->[3]{$key} = file_contents( $this,
252             $entry,
253             $this->[0]{'buf_size'} );
254             }
255 0           return($this->[3]{$key});
256             #-------------------------------------------------------------------
257             }
258              
259             #=======================================================================
260             # Not supported functions, required for tied hash implementation.
261             #=======================================================================
262              
263             sub DESTROY {
264 0     0     my ( $this ) = @_;
265             #-------------------------------------------------------------------
266 0 0         if ( $this->[0]{'dbg'} >= 4 ) { debug(4, "IN: nothing to do"); }
  0            
267 0 0         if ( $this->[0]{'dbg'} >= 4 ) { debug(4, "OUT"); }
  0            
268             #-------------------------------------------------------------------
269             }
270              
271             #-----------------------------------------------------------------------
272              
273             sub STORE {
274 0     0     my ( $this, $key, $value ) = @_;
275             #-------------------------------------------------------------------
276 0 0         if ( $this->[0]{'dbg'} >= 4 ) { debug(4, "IN: not supported"); }
  0            
277 0 0         if ( $this->[0]{'dbg'} >= 4 ) { debug(4, "OUT"); }
  0            
278             #-------------------------------------------------------------------
279             }
280              
281             #-----------------------------------------------------------------------
282              
283             sub DELETE {
284 0     0     my ( $this, $key ) = @_;
285             #-------------------------------------------------------------------
286 0 0         if ( $this->[0]{'dbg'} >= 4 ) { debug(4, "IN: not supported"); }
  0            
287 0 0         if ( $this->[0]{'dbg'} >= 4 ) { debug(4, "OUT"); }
  0            
288             #-------------------------------------------------------------------
289             }
290              
291             #-----------------------------------------------------------------------
292              
293             sub CLEAR {
294 0     0     my ( $this ) = @_;
295             #-------------------------------------------------------------------
296 0 0         if ( $this->[0]{'dbg'} >= 4 ) { debug(4, "IN: not supported"); }
  0            
297 0 0         if ( $this->[0]{'dbg'} >= 4 ) { debug(4, "OUT"); }
  0            
298             #-------------------------------------------------------------------
299             }
300              
301             #=======================================================================
302             # Not required for tied hash implementation.
303             # These functions are required for this particular implementation.
304             #=======================================================================
305              
306             sub KEYS {
307 0     0 0   my ( $this ) = @_;
308             #-------------------------------------------------------------------
309 0 0         if ( $this->[0]{'dbg'} >= 4 ) { debug(4, "IN"); }
  0            
310 0 0         if ( $this->[0]{'dbg'} >= 4 ) { debug(4, "OUT"); }
  0            
311             #-------------------------------------------------------------------
312 0           return(@{$this->[1]});
  0            
313             }
314              
315             #-----------------------------------------------------------------------
316             # Just return the version of the class implementation
317             #-----------------------------------------------------------------------
318              
319             sub version {
320 0     0 0   my ( $this ) = @_;
321             #-------------------------------------------------------------------
322 0 0         if ( $this->[0]{'dbg'} >= 4 ) { debug(4, "IN"); }
  0            
323 0 0         if ( $this->[0]{'dbg'} >= 4 ) { debug(4, "OUT"); }
  0            
324             #-------------------------------------------------------------------
325 0           return($VERSION);
326             }
327              
328             #-----------------------------------------------------------------------
329             # Fetch directory contents
330             #-----------------------------------------------------------------------
331              
332             sub dir_contents {
333 0     0 0   my ( $this ) = @_;
334 0 0         if ( $this->[0]{'dbg'} >= 4 )
335 0           { debug(4, ['DIR'], [$this->[0]{'dir'}]); }
336             #-------------------------------------------------------------------
337             # Read in contents of the directory through randomized handler
338             #-------------------------------------------------------------------
339 0           $symbol = gensym();
340 0 0         if ( ! opendir ( $symbol, $this->[0]{'dir'} ) ) {
341 0 0         if ( $this->[0]{'dbg'} )
342 0           { debug(1, "Failed to open dir $this->[0]{'dir'}"); }
343 0           return(undef);
344             }
345 0           my @entries = sort grep { ! /^\.+$/ } readdir ( $symbol );
  0            
346             #-------------------------------------------------------------------
347             # Determine what every entry is
348             #-------------------------------------------------------------------
349 0           my %contents;
350 0           foreach my $entry ( @entries ) {
351 0           (my $element = "$this->[0]{'dir'}/$entry") =~ s{/+}{/}g;
352 0           my @stat = stat($element);
353 0           my $mode = undef;
354 0 0         if ( S_ISDIR($stat[2]) ) { $mode = "DIR"; }
  0            
355 0 0         if ( S_ISREG($stat[2]) ) { $mode = "FILE"; }
  0            
356 0 0         if ( $mode eq "DIR" ) {
    0          
357 0           $contents{$entry} = {}; # HASH - directory
358             } elsif ( $mode eq "FILE" ) {
359 0           $contents{$entry} = ''; # SCALAR - file contents
360             } else {
361 0           $contents{$entry} = undef; # UNDEF - hmm...
362             }
363             }
364 0           closedir($symbol);
365             #-------------------------------------------------------------------
366             # Store data in the object
367             #-------------------------------------------------------------------
368 0           $this->[1] = \@entries; # Store list of entries
369 0           $this->[2] = 0; # What is the number of current element
370 0           $this->[3] = \%contents; # Hashed contents
371             #-------------------------------------------------------------------
372 0 0         if ( $this->[0]{'dbg'} >= 6 )
373 0           { debug(6, ['ENTRIES'], [\@entries]); }
374 0 0         if ( $this->[0]{'dbg'} >= 4 ) { debug(4, "OUT"); }
  0            
375             }
376              
377             #-----------------------------------------------------------------------
378             # Fetch file contents
379             #-----------------------------------------------------------------------
380              
381             sub file_contents {
382 0     0 0   my ( $this, $file, $buf_size ) = @_;
383 0 0         if ( $this->[0]{'dbg'} >= 4 ) { debug(4, ['FILE'], [$file]); }
  0            
384             #-------------------------------------------------------------------
385             # Our implementation is general, so we will call different
386             # subroutines on different files. $type is a switcher for handlers
387             #-------------------------------------------------------------------
388 0           my $type = undef;
389 0           foreach my $re ( keys %file_type ) {
390 0 0         next unless ( $file =~ m{$re} );
391 0           $type = $re; last;
  0            
392             }
393 0 0         if ( ! defined($type) ) { $type = 'default'; }
  0            
394 0 0         if ( $this->[0]{'dbg'} >= 6 ) {debug(6, ['buf_size'], [$buf_size]);}
  0            
395 0           my $contents = &{$file_type{$type}}( $file,
  0            
396             $this->[0]{'dbg'},
397             $buf_size );
398 0           return($contents);
399             #-------------------------------------------------------------------
400             }
401              
402              
403             #-----------------------------------------------------------------------
404             # Plain Old Documentation
405             #-----------------------------------------------------------------------
406              
407             =head1 NAME
408              
409             Tie::FileSystem - Access file system via a Perl hash
410              
411             =head1 SYNOPSIS
412              
413             use Tie::FileSystem;
414             use Data::Dumper;
415              
416             my %data;
417             tie %data, "Tie::FileSystem", ( 'dir' => "/" );
418             print Dumper($data{'etc'}{'passwd'});
419              
420             =head1 DESCRIPTION
421              
422             Tie::FileSystem represents file system as a Perl hash. Each hash key
423             corresponds to name of a directory or a file. For example, for a file
424             "/etc/passwd" it will be $data{'etc'}{'passwd'}. Contents of the file
425             "/etc/passwd" becomes a value corresponding to the
426             $data{'etc'}{'passwd'}.
427              
428             Standard handling procedure for directories is to store a listing of
429             files in the directory as keys. Standard procedure for files is to store
430             a contents of the file in the scalar value.
431              
432             For certain files with known structure it is possible to define
433             subroutines for special handling. "Tie::FileSystem::System" defines
434             subroutines for handling system files and, for starters, has 'passwd'
435             handling subroutine. "/etc/passwd" can be represented asa hash with
436             following structure: $data{'etc'}{'passwd'}{$username}{$field}.
437              
438             =head2 Options
439              
440             tie %data, "Tie::FileSystem",
441             ( 'dbg' => 0, 'buf_size' => 10, 'dir' => "/" );
442            
443             'dbg' - level of debug output
444             0 - 'SILENT', default # No output at all, ERRORs are suppressed
445             1 - 'ERROR' # ERRORs are printed to STDERR
446             2 - 'WARNING' # WARNINGs are printed to STDERR
447             3 - 'INFO' # Information messages
448             4 - 'D:IN/OUT' # Important variables
449             5 - 'D:LOGIC' # Logical desicions
450             6 - 'D:VARS' # Variables
451              
452             'buf_size' - buffer limit for file reading
453              
454             'dir' - directory to tie to
455              
456             =head2 Public Methods
457              
458             None.
459              
460             =head1 PLATFORMS
461              
462             Debian 3.1, Perl, v5.8.8.
463              
464             Windows XP, ActiveState Perl, v5.8.8.
465              
466             =head1 CAVEATS
467              
468             The module is read only and does not permit overwrite or delete
469             files.
470              
471             Under Windows '/' corresponds to 'C:'.
472              
473             If you try to tie hash %data to '/' and then print Dumper(%data),
474             module will traverse the entire file system on demand!
475              
476             =head1 BUGS
477              
478             None known.
479              
480             =head1 AUTHOR
481              
482             Vadim V. Kouevda, initdotd@gmail.com
483              
484             =head1 LICENSE and COPYRIGHT
485              
486             Copyright (c) 2003-2007, Vadim V. Kouevda, "KAITS, Inc."
487              
488             This library is free software; you may redistribute it and/or modify it
489             under the same terms as Perl itself.
490              
491             These terms are your choice of any of (1) the Perl Artistic Licence, or
492             (2) version 2 of the GNU General Public License as published by the
493             Free Software Foundation, or (3) any later version of the GNU General
494             Public License.
495              
496             This library is distributed in the hope that it will be useful, but
497             WITHOUT ANY WARRANTY; without even the implied warranty of
498             MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
499             General Public License for more details.
500              
501             You should have received a copy of the GNU General Public License along
502             with this library program; it should be in the file COPYING. If not,
503             write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
504             Boston, MA 02111 USA
505              
506             For licensing inquiries, contact the author at initdotd@gmail.com
507              
508             =head1 WARRANTY
509              
510             Module comes with ABSOLUTELY NO WARRANTY. For details, see the license.
511              
512             =head1 AVAILABILITY
513              
514             The latest version can be obtained from CPAN
515              
516             =head1 SEE ALSO
517              
518             Tie::FileSystem::System(3), Tie::File(3)
519              
520             =cut
521              
522             #-----------------------------------------------------------------------
523             # $Id: FileSystem.pm,v 2.15 2007/03/21 00:11:01 vadim Exp $
524             #-----------------------------------------------------------------------
525             # $Log: FileSystem.pm,v $
526             # Revision 2.15 2007/03/21 00:11:01 vadim
527             # Cleaning POD from KA::Tie::Dir references
528             #
529             # Revision 2.13 2007/03/20 21:45:19 vadim
530             # Fixed small insignificant bug with debuging in NEXTKEY
531             #
532             # Revision 2.12 2007/03/20 21:20:50 vadim
533             # Upon suggestion of Steven Schubiger (schubiger@gmail.com) added indents
534             # to displaying code in POD.
535             #
536             # Revision 2.11 2007/03/20 21:17:08 vadim
537             # Convert to Tie:FileSystem name space
538             #-----------------------------------------------------------------------
539             1;