File Coverage

blib/lib/Filesys/Virtual/Async/inMemory.pm
Criterion Covered Total %
statement 26 455 5.7
branch 1 236 0.4
condition 0 33 0.0
subroutine 9 42 21.4
pod 31 31 100.0
total 67 797 8.4


line stmt bran cond sub pod time code
1             # Declare our package
2             package Filesys::Virtual::Async::inMemory;
3 1     1   1645 use strict; use warnings;
  1     1   3  
  1         47  
  1         7  
  1         2  
  1         41  
4              
5             # Initialize our version
6 1     1   17 use vars qw( $VERSION );
  1         2  
  1         88  
7             $VERSION = '0.02';
8              
9             # Set our parent
10 1     1   6 use base 'Filesys::Virtual::Async';
  1         1  
  1         1160  
11              
12             # get some system constants
13 1     1   1396 use Errno qw( :POSIX ); # ENOENT EISDIR etc
  1         2  
  1         658  
14 1     1   7 use Fcntl qw( :DEFAULT :mode :seek ); # S_IFREG S_IFDIR, O_SYNC O_LARGEFILE etc
  1         2  
  1         952  
15              
16             # get some handy stuff
17 1     1   8 use File::Spec;
  1         2  
  1         33  
18              
19             # create our virtual FHs
20 1     1   1057 use IO::Scalar;
  1         15722  
  1         87  
21              
22             # Set some constants
23             BEGIN {
24 1 50   1   6 if ( ! defined &DEBUG ) { *DEBUG = sub () { 0 } }
  1         6314  
25             }
26              
27             # creates a new instance
28             sub new {
29 0     0 1   my $class = shift;
30              
31             # The options hash
32 0           my %opt;
33              
34             # Support passing in a hash ref or a regular hash
35 0 0 0       if ( ( @_ & 1 ) and ref $_[0] and ref( $_[0] ) eq 'HASH' ) {
      0        
36 0           %opt = %{ $_[0] };
  0            
37             } else {
38             # Sanity checking
39 0 0         if ( @_ & 1 ) {
40 0           warn __PACKAGE__ . ' requires an even number of options passed to new()';
41 0           return;
42             }
43              
44 0           %opt = @_;
45             }
46              
47             # lowercase keys
48 0           %opt = map { lc($_) => $opt{$_} } keys %opt;
  0            
49              
50             # set the readonly mode
51 0 0 0       if ( exists $opt{'readonly'} and defined $opt{'readonly'} ) {
52 0 0         $opt{'readonly'} = $opt{'readonly'} ? 1 : 0;
53             } else {
54 0           if ( DEBUG ) {
55             warn 'using default READONLY = false';
56             }
57              
58 0           $opt{'readonly'} = 0;
59             }
60              
61             # get our filesystem :)
62 0 0 0       if ( exists $opt{'filesystem'} and defined $opt{'filesystem'} ) {
63             # validate it
64 0 0         if ( ! _validate_fs( $opt{'filesystem'} ) ) {
65 0           warn 'invalid filesystem passed to new()';
66 0           return;
67             }
68             } else {
69 0           if ( DEBUG ) {
70             warn 'using default FILESYSTEM = empty';
71             }
72              
73 0           $opt{'filesystem'} = {
74             File::Spec->rootdir() => {
75             'mode' => oct( '040755' ),
76             'ctime' => time(),
77             },
78             };
79             }
80              
81             # set the cwd
82 0 0 0       if ( exists $opt{'cwd'} and defined $opt{'cwd'} ) {
83             # FIXME validate it
84             } else {
85 0           if ( DEBUG ) {
86             warn 'using default CWD = ' . File::Spec->rootdir();
87             }
88              
89 0           $opt{'cwd'} = File::Spec->rootdir();
90             }
91              
92             # create our instance
93 0           my $self = {
94             'fs' => $opt{'filesystem'},
95             'readonly' => $opt{'readonly'},
96             'cwd' => $opt{'cwd'},
97             };
98 0           bless $self, $class;
99              
100 0           return $self;
101             }
102              
103             #my %files = (
104             # '/' => {
105             # mode => oct( '040755' ),
106             # ctime => time()-1000,
107             # },
108             # '/a' => {
109             # data => "File 'a'.\n",
110             # mode => oct( 100755 ),
111             # ctime => time()-2000,
112             # },
113             # '/b' => {
114             # data => "This is file 'b'.\n",
115             # mode => oct( 100644 ),
116             # ctime => time()-1000,
117             # },
118             # '/foo' => {
119             # mode => oct( '040755' ),
120             # ctime => time()-3000,
121             # },
122             # '/foo/bar' => {
123             # data => "APOCAL is the best!\nJust kidding :)\n",
124             # mode => oct( 100755 ),
125             # ctime => time()-5000,
126             # },
127             #);
128              
129             # validates a filesystem struct
130             sub _validate_fs {
131 0     0     my $fs = shift;
132              
133             # FIXME add validation
134 0           return 1;
135             }
136              
137             # simple accessor
138             sub _fs {
139 0     0     return shift->{'fs'};
140             }
141             sub readonly {
142 0     0 1   my $self = shift;
143 0           my $ro = shift;
144 0 0         if ( defined $ro ) {
145 0 0         $self->{'readonly'} = $ro ? 1 : 0;
146             }
147 0           return $self->{'readonly'};
148             }
149              
150             sub cwd {
151 0     0 1   my( $self, $cwd, $cb ) = @_;
152              
153             # sanitize the path
154 0           $cwd = File::Spec->canonpath( $cwd );
155              
156             # Get or set?
157 0 0         if ( ! defined $cwd ) {
158 0 0         if ( defined $cb ) {
159 0           $cb->( $self->{'cwd'} );
160 0           return;
161             } else {
162 0           return $self->{'cwd'};
163             }
164             }
165              
166             # actually change our cwd!
167 0           $self->{'cwd'} = $cwd;
168 0 0         if ( defined $cb ) {
169 0           $cb->( $cwd );
170 0           return;
171             } else {
172 0           return $cwd;
173             }
174             }
175              
176             sub open {
177 0     0 1   my( $self, $path, $flags, $mode, $callback ) = @_;
178              
179             # FIXME fix relative path/sanitize path?
180              
181             # determine if we should be using callback mode
182 0 0         if ( ref( $self ) ne __PACKAGE__ ) {
183 0 0         if ( $self->can( '_open' ) ) {
184 0           my $scalar_ref = $self->_open( $path );
185 0 0         if ( defined $scalar_ref ) {
186 0           my $fh = IO::Scalar->new( $scalar_ref );
187 0 0         if ( defined $fh ) {
188 0           $callback->( $fh );
189             } else {
190 0           $callback->( -EIO() );
191             }
192             } else {
193 0           $callback->( -EINVAL() );
194             }
195             } else {
196 0           $callback->( -ENOSYS() );
197             }
198 0           return;
199             }
200              
201             # make sure we're opening a real file
202 0 0         if ( exists $self->_fs->{ $path } ) {
203 0 0         if ( ! S_ISDIR( $self->_fs->{ $path }{'mode'} ) ) {
204             # return a $fh object
205 0           my $fh = IO::Scalar->new( \$self->_fs->{ $path }->{'data'} );
206 0 0         if ( defined $fh ) {
207 0           $callback->( $fh );
208             } else {
209 0           $callback->( -EIO() );
210             }
211             } else {
212             # path is a directory!
213 0           $callback->( -EISDIR() );
214             }
215             } else {
216             # path does not exist
217 0           $callback->( -ENOENT() );
218             }
219              
220 0           return;
221             }
222              
223             sub close {
224 0     0 1   my( $self, $fh, $callback ) = @_;
225              
226             # cleanly close the FH
227 0           $fh->close;
228 0           $callback->( 0 );
229              
230 0           return;
231             }
232              
233             sub read {
234             # aio_read $fh,$offset,$length, $data,$dataoffset, $callback->($retval)
235             # have to leave @_ alone so caller will get proper $buffer reference :(
236 0     0 1   my $self = shift;
237 0           my $fh = shift;
238              
239             # seek to $offset
240 0           $fh->seek( $_[0], SEEK_SET );
241              
242             # read the data from the fh!
243 0           my $buf = '';
244 0           my $ret = $fh->read( $buf, $_[1], 0 );
245 0 0 0       if ( ! defined $ret or $ret == 0 ) {
246 0           $_[4]->( -EIO() );
247             } else {
248             # stuff the read data into the true buffer, determined by dataoffset
249 0           substr( $_[2], $_[3], $ret, $buf );
250              
251             # inform the callback of success
252 0           $_[4]->( $ret );
253             }
254              
255 0           return;
256             }
257              
258             sub write {
259 0     0 1   my( $self, $fh, $offset, $length, $data, $dataoffset, $callback ) = @_;
260              
261             # are we readonly?
262 0 0         if ( $self->readonly ) {
263 0           $callback->( -EROFS() );
264             } else {
265             # determine if we should be using callback mode
266 0 0         if ( ref( $self ) ne __PACKAGE__ ) {
267 0 0         if ( $self->can( '_write' ) ) {
268             # FIXME should we also use dataoffset?
269 0           $callback->( $self->_write( $fh, $offset, $length, $data ) );
270             } else {
271 0           $callback->( -ENOSYS() );
272             }
273 0           return;
274             }
275              
276             # seek to $offset
277 0           $fh->seek( $offset, SEEK_SET );
278              
279             # write the data!
280 0           my $ret = $fh->write( $data, $length, $dataoffset );
281 0 0         if ( ! $ret ) {
282 0           $callback->( -EIO() );
283             } else {
284             # return length
285 0           $callback->( length( $data ) - $dataoffset );
286             }
287             }
288              
289 0           return;
290             }
291              
292             sub sendfile {
293 0     0 1   my( $self, $out_fh, $in_fh, $in_offset, $length, $callback ) = @_;
294              
295             # start by reading $length from $in_fh
296 0           my $buf = '';
297 0           my $ret = $in_fh->read( $buf, $length, $in_offset );
298 0 0 0       if ( ! defined $ret or $ret == 0 ) {
299 0           $callback->( -EIO() );
300             } else {
301             # write it to $out_fh ( at the end )
302 0           $out_fh->seek( 0, SEEK_END );
303 0           $ret = $out_fh->write( $buf, $length );
304 0 0         if ( $ret ) {
305 0           $callback->( $length );
306             } else {
307 0           $callback->( -EIO() );
308             }
309             }
310              
311 0           return;
312             }
313              
314             sub readahead {
315 0     0 1   my( $self, $fh, $offset, $length, $callback ) = @_;
316              
317             # not implemented, always return success
318 0           $callback->( 0 );
319 0           return;
320             }
321              
322             sub stat {
323 0     0 1   my( $self, $path, $callback ) = @_;
324              
325             # FIXME we don't support array/fh mode because it would require insane amounts of munging the paths
326 0 0         if ( ref $path ) {
327 0           if ( DEBUG ) {
328             warn 'Passing a REF to stat() is not supported!';
329             }
330 0           $callback->( -ENOSYS() );
331 0           return;
332             }
333              
334             # FIXME fix relative path/sanitize path?
335              
336             # determine if we should be using callback mode
337 0 0         if ( ref( $self ) ne __PACKAGE__ ) {
338 0 0         if ( $self->can( '_stat' ) ) {
339 0           my $ret = $self->_stat( $path );
340 0 0         if ( defined $ret ) {
341 0           $callback->( $ret );
342             } else {
343 0           $callback->( -ENOENT() );
344             }
345             } else {
346 0           $callback->( -ENOSYS() );
347             }
348 0           return;
349             }
350              
351             # gather the proper information
352 0 0         if ( exists $self->_fs->{ $path } ) {
353             ## no critic ( ProhibitAccessOfPrivateData )
354 0           my $info = $self->_fs->{ $path };
355              
356 0 0         my $size = exists $info->{'data'} ? length( $info->{'data'} ) : 0;
357 0           my $modes = $info->{'mode'};
358              
359 0           my ($dev, $ino, $rdev, $blocks, $gid, $uid, $nlink, $blksize) = ( 0, 0, 0, 1, (split( /\s+/, $) ))[0], $>, 1, 1024 );
360 0 0         if ( S_ISDIR( $modes ) ) {
361             # count the children directories
362 0           $nlink = 2; # start with 2 ( . and .. )
363              
364             # FIXME make this portable!
365 0 0         $nlink += grep { $_ =~ /^$path\/?[^\/]+$/ and S_ISDIR( $self->_fs->{ $_ }{'mode'} ) } ( keys %{ $self->_fs } );
  0            
  0            
366             }
367              
368 0 0         $gid = $info->{'gid'} if exists $info->{'gid'};
369 0 0         $uid = $info->{'uid'} if exists $info->{'uid'};
370 0           my ($atime, $ctime, $mtime);
371 0           $atime = $ctime = $mtime = $info->{'ctime'};
372 0 0         $atime = $info->{'atime'} if exists $info->{'atime'};
373 0 0         $mtime = $info->{'mtime'} if exists $info->{'mtime'};
374              
375             # finally, return the darn data!
376 0           $callback->( [ $dev, $ino, $modes, $nlink, $uid, $gid, $rdev, $size, $atime, $mtime, $ctime, $blksize, $blocks ] );
377             } else {
378             # path does not exist
379 0           $callback->( -ENOENT() );
380             }
381              
382 0           return;
383             }
384              
385             sub lstat {
386 0     0 1   my( $self, $path, $callback ) = @_;
387              
388             # FIXME unimplemented because of complexity
389 0           $callback->( -ENOSYS() );
390              
391 0           return;
392             }
393              
394             sub utime {
395 0     0 1   my( $self, $path, $atime, $mtime, $callback ) = @_;
396              
397             # FIXME we don't support fh mode because it would require insane amounts of munging the paths
398 0 0         if ( ref $path ) {
399 0           if ( DEBUG ) {
400             warn 'Passing a REF to utime() is not supported!';
401             }
402 0           $callback->( -ENOSYS() );
403 0           return;
404             }
405              
406             # are we readonly?
407 0 0         if ( $self->readonly ) {
408 0           $callback->( -EROFS() );
409 0           return;
410             }
411              
412             # FIXME fix relative path/sanitize path?
413              
414             # determine if we should be using callback mode
415 0 0         if ( ref( $self ) ne __PACKAGE__ ) {
416 0 0         if ( $self->can( '_utime' ) ) {
417 0           $callback->( $self->_utime( $path, $atime, $mtime ) );
418             } else {
419 0           $callback->( -ENOSYS() );
420             }
421 0           return;
422             }
423              
424 0 0         if ( exists $self->_fs->{ $path } ) {
425             # okay, update the time
426 0 0         if ( ! defined $atime ) { $atime = time() }
  0            
427 0 0         if ( ! defined $mtime ) { $mtime = $atime }
  0            
428 0           $self->_fs->{ $path }{'atime'} = $atime;
429 0           $self->_fs->{ $path }{'mtime'} = $mtime;
430              
431             # successful update of time!
432 0           $callback->( 0 );
433             } else {
434             # path does not exist
435 0           $callback->( -ENOENT() );
436             }
437              
438 0           return;
439             }
440              
441             sub chown {
442 0     0 1   my( $self, $path, $uid, $gid, $callback ) = @_;
443              
444             # FIXME we don't support fh mode because it would require insane amounts of munging the paths
445 0 0         if ( ref $path ) {
446 0           if ( DEBUG ) {
447             warn 'Passing a REF to chown() is not supported!';
448             }
449 0           $callback->( -ENOSYS() );
450 0           return;
451             }
452              
453             # are we readonly?
454 0 0         if ( $self->readonly ) {
455 0           $callback->( -EROFS() );
456 0           return;
457             }
458              
459             # FIXME fix relative path/sanitize path?
460              
461             # determine if we should be using callback mode
462 0 0         if ( ref( $self ) ne __PACKAGE__ ) {
463 0 0         if ( $self->can( '_chown' ) ) {
464 0           $callback->( $self->_chown( $path, $uid, $gid ) );
465             } else {
466 0           $callback->( -ENOSYS() );
467             }
468 0           return;
469             }
470              
471 0 0         if ( exists $self->_fs->{ $path } ) {
472             # okay, update the ownerships!
473 0 0 0       if ( defined $uid and $uid > -1 ) {
474 0           $self->_fs->{ $path }{'uid'} = $uid;
475             }
476 0 0 0       if ( defined $gid and $gid > -1 ) {
477 0           $self->_fs->{ $path }{'gid'} = $gid;
478             }
479              
480             # successful update of ownership!
481 0           $callback->( 0 );
482             } else {
483             # path does not exist
484 0           $callback->( -ENOENT() );
485             }
486              
487 0           return;
488             }
489              
490             sub truncate {
491 0     0 1   my( $self, $path, $offset, $callback ) = @_;
492              
493             # FIXME we don't support fh mode because it would require insane amounts of munging the paths
494 0 0         if ( ref $path ) {
495 0           if ( DEBUG ) {
496             warn 'Passing a REF to truncate() is not supported!';
497             }
498 0           $callback->( -ENOSYS() );
499 0           return;
500             }
501              
502             # are we readonly?
503 0 0         if ( $self->readonly ) {
504 0           $callback->( -EROFS() );
505 0           return;
506             }
507              
508             # FIXME fix relative path/sanitize path?
509              
510             # determine if we should be using callback mode
511 0 0         if ( ref( $self ) ne __PACKAGE__ ) {
512 0 0         if ( $self->can( '_truncate' ) ) {
513 0           $callback->( $self->_truncate( $path, $offset ) );
514             } else {
515 0           $callback->( -ENOSYS() );
516             }
517 0           return;
518             }
519              
520 0 0         if ( exists $self->_fs->{ $path } ) {
521 0 0         if ( ! S_ISDIR( $self->_fs->{ $path }{'mode'} ) ) {
522             # valid file, proceed with the truncate!
523              
524             # sanity check, offset cannot be bigger than the length of the file!
525 0 0         if ( $offset > length( $self->_fs->{ $path }{'data'} ) ) {
526 0           $callback->( -EINVAL() );
527             } else {
528             # did we reach the end of the file?
529 0 0         if ( $offset != length( $self->_fs->{ $path }{'data'} ) ) {
530             # ok, truncate our copy!
531 0           $self->_fs->{ $path }{'data'} = substr( $self->_fs->{ $path }{'data'}, 0, $offset );
532             }
533              
534             # successfully truncated
535 0           $callback->( 0 );
536             }
537             } else {
538             # path is a directory!
539 0           $callback->( -EISDIR() );
540             }
541             } else {
542             # path does not exist
543 0           $callback->( -ENOENT() );
544             }
545              
546 0           return;
547             }
548              
549             sub chmod {
550 0     0 1   my( $self, $path, $mode, $callback ) = @_;
551              
552             # FIXME we don't support fh mode because it would require insane amounts of munging the paths
553 0 0         if ( ref $path ) {
554 0           if ( DEBUG ) {
555             warn 'Passing a REF to chmod() is not supported!';
556             }
557 0           $callback->( -ENOSYS() );
558 0           return;
559             }
560              
561             # are we readonly?
562 0 0         if ( $self->readonly ) {
563 0           $callback->( -EROFS() );
564 0           return;
565             }
566              
567             # FIXME fix relative path/sanitize path?
568              
569             # determine if we should be using callback mode
570 0 0         if ( ref( $self ) ne __PACKAGE__ ) {
571 0 0         if ( $self->can( '_chmod' ) ) {
572 0           $callback->( $self->_chmod( $path, $mode ) );
573             } else {
574 0           $callback->( -ENOSYS() );
575             }
576 0           return;
577             }
578              
579 0 0         if ( exists $self->_fs->{ $path } ) {
580             # okay, update the mode!
581 0           $self->_fs->{ $path }{'mode'} = $mode;
582              
583             # successful update of mode!
584 0           $callback->( 0 );
585             } else {
586             # path does not exist
587 0           $callback->( -ENOENT() );
588             }
589              
590 0           return;
591             }
592              
593             sub unlink {
594 0     0 1   my( $self, $path, $callback ) = @_;
595              
596             # are we readonly?
597 0 0         if ( $self->readonly ) {
598 0           $callback->( -EROFS() );
599 0           return;
600             }
601              
602             # FIXME fix relative path/sanitize path?
603              
604             # determine if we should be using callback mode
605 0 0         if ( ref( $self ) ne __PACKAGE__ ) {
606 0 0         if ( $self->can( '_unlink' ) ) {
607 0           $callback->( $self->_unlink( $path ) );
608             } else {
609 0           $callback->( -ENOSYS() );
610             }
611 0           return;
612             }
613              
614 0 0         if ( exists $self->_fs->{ $path } ) {
615 0 0         if ( ! S_ISDIR( $self->_fs->{ $path }{'mode'} ) ) {
616             # valid file, proceed with the deletion!
617 0           delete $self->_fs->{ $path };
618              
619             # successful deletion!
620 0           $callback->( 0 );
621             } else {
622             # path is a directory!
623 0           $callback->( -EISDIR() );
624             }
625             } else {
626             # path does not exist
627 0           $callback->( -ENOENT() );
628             }
629              
630 0           return;
631             }
632              
633             sub mknod {
634 0     0 1   my( $self, $path, $mode, $dev, $callback ) = @_;
635              
636             # are we readonly?
637 0 0         if ( $self->readonly ) {
638 0           $callback->( -EROFS() );
639 0           return;
640             }
641              
642             # FIXME fix relative path/sanitize path?
643              
644             # we only allow regular files to be created
645 0 0         if ( $dev == 0 ) {
646             # make sure mode is proper
647 0           $mode = $mode | oct( '100000' );
648             } else {
649             # unsupported mode
650 0           $callback->( -EINVAL() );
651 0           return;
652             }
653              
654             # determine if we should be using callback mode
655 0 0         if ( ref( $self ) ne __PACKAGE__ ) {
656 0 0         if ( $self->can( '_mknod' ) ) {
657 0           $callback->( $self->_mknod( $path, $mode ) );
658             } else {
659 0           $callback->( -ENOSYS() );
660             }
661 0           return;
662             }
663              
664 0 0 0       if ( exists $self->_fs->{ $path } or $path eq '.' or $path eq '..' ) {
      0        
665             # already exists!
666 0           $callback->( -EEXIST() );
667             } else {
668             # should we add validation to make sure all parents already exist
669             # seems like touch() and friends check themselves, so we don't have to do it...
670              
671 0           $self->_fs->{ $path } = {
672             mode => $mode,
673             ctime => time(),
674             data => "",
675             };
676              
677             # successful creation!
678 0           $callback->( 0 );
679             }
680              
681 0           return;
682             }
683              
684             sub link {
685 0     0 1   my( $self, $srcpath, $dstpath, $callback ) = @_;
686              
687             # FIXME unimplemented because of complexity
688 0           $callback->( -ENOSYS() );
689              
690 0           return;
691             }
692              
693             sub symlink {
694 0     0 1   my( $self, $srcpath, $dstpath, $callback ) = @_;
695              
696             # FIXME unimplemented because of complexity
697 0           $callback->( -ENOSYS() );
698              
699 0           return;
700             }
701              
702             sub readlink {
703 0     0 1   my( $self, $path, $callback ) = @_;
704              
705             # FIXME unimplemented because of complexity
706 0           $callback->( -ENOSYS() );
707              
708 0           return;
709             }
710              
711             sub rename {
712 0     0 1   my( $self, $srcpath, $dstpath, $callback ) = @_;
713              
714             # are we readonly?
715 0 0         if ( $self->readonly ) {
716 0           $callback->( -EROFS() );
717 0           return;
718             }
719              
720             # FIXME fix relative path/sanitize path?
721              
722             # determine if we should be using callback mode
723 0 0         if ( ref( $self ) ne __PACKAGE__ ) {
724 0 0         if ( $self->can( '_rename' ) ) {
725 0           $callback->( $self->_rename( $srcpath, $dstpath ) );
726             } else {
727 0           $callback->( -ENOSYS() );
728             }
729 0           return;
730             }
731              
732 0 0         if ( exists $self->_fs->{ $srcpath } ) {
733 0 0         if ( ! exists $self->_fs->{ $dstpath } ) {
734             # should we add validation to make sure all parents already exist
735             # seems like mv() and friends check themselves, so we don't have to do it...
736              
737             # proceed with the rename!
738 0           $self->_fs->{ $dstpath } = delete $self->_fs->{ $srcpath };
739              
740 0           $callback->( 0 );
741             } else {
742             # destination already exists!
743 0           $callback->( -EEXIST() );
744             }
745             } else {
746             # path does not exist
747 0           $callback->( -ENOENT() );
748             }
749              
750 0           return;
751             }
752              
753             sub mkdir {
754 0     0 1   my( $self, $path, $mode, $callback ) = @_;
755              
756             # are we readonly?
757 0 0         if ( $self->readonly ) {
758 0           $callback->( -EROFS() );
759 0           return;
760             }
761              
762             # FIXME fix relative path/sanitize path?
763              
764             # make sure mode is proper
765 0           $mode = $mode | oct( '040000' );
766              
767             # determine if we should be using callback mode
768 0 0         if ( ref( $self ) ne __PACKAGE__ ) {
769 0 0         if ( $self->can( '_mkdir' ) ) {
770 0           $callback->( $self->_mkdir( $path, $mode ) );
771             } else {
772 0           $callback->( -ENOSYS() );
773             }
774 0           return;
775             }
776              
777 0 0         if ( exists $self->_fs->{ $path } ) {
778             # already exists!
779 0           $callback->( -EEXIST() );
780             } else {
781             # should we add validation to make sure all parents already exist
782             # seems like mkdir() and friends check themselves, so we don't have to do it...
783              
784             # create the directory!
785 0           $self->_fs->{ $path } = {
786             mode => $mode,
787             ctime => time(),
788             };
789              
790             # successful creation!
791 0           $callback->( 0 );
792             }
793              
794 0           return;
795             }
796              
797             sub rmdir {
798 0     0 1   my( $self, $path, $callback ) = @_;
799              
800             # are we readonly?
801 0 0         if ( $self->readonly ) {
802 0           $callback->( -EROFS() );
803 0           return;
804             }
805              
806             # FIXME fix relative path/sanitize path?
807              
808             # determine if we should be using callback mode
809 0 0         if ( ref( $self ) ne __PACKAGE__ ) {
810 0 0         if ( $self->can( '_rmdir' ) ) {
811 0           $callback->( $self->_rmdir( $path ) );
812             } else {
813 0           $callback->( -ENOSYS() );
814             }
815 0           return;
816             }
817              
818 0 0         if ( exists $self->_fs->{ $path } ) {
819 0 0         if ( S_ISDIR( $self->_fs->{ $path }{'mode'} ) ) {
820             # valid directory, does this directory have any children ( files, subdirs ) ??
821 0           my $children = grep { $_ =~ /^$path/ } ( keys %{ $self->_fs } );
  0            
  0            
822 0 0         if ( $children == 1 ) {
823 0           delete $self->_fs->{ $path };
824              
825             # successful deletion!
826 0           $callback->( 0 );
827             } else {
828             # need to delete children first!
829 0           $callback->( -ENOTEMPTY() );
830             }
831             } else {
832             # path is not a directory!
833 0           $callback->( -ENOTDIR() );
834             }
835             } else {
836             # path does not exist
837 0           $callback->( -ENOENT() );
838             }
839              
840 0           return;
841             }
842              
843             sub readdir {
844 0     0 1   my( $self, $path, $callback ) = @_;
845              
846             # FIXME fix relative path/sanitize path?
847              
848             # determine if we should be using callback mode
849 0 0         if ( ref( $self ) ne __PACKAGE__ ) {
850 0 0         if ( $self->can( '_readdir' ) ) {
851 0           $callback->( $self->_readdir( $path ) );
852             } else {
853 0           $callback->( -ENOSYS() );
854             }
855 0           return;
856             }
857              
858 0 0         if ( exists $self->_fs->{ $path } ) {
859 0 0         if ( S_ISDIR( $self->_fs->{ $path }{'mode'} ) ) {
860             # construct all the data in this directory
861             # FIXME make this portable!
862 0           my @list = grep { $_ =~ /^$path\/?[^\/]+$/ } ( keys %{ $self->_fs } );
  0            
  0            
863 0           s/^$path\/?// for @list;
864              
865             # no need to add "." and ".."
866              
867             # return the list!
868 0           $callback->( \@list );
869             } else {
870             # path is not a directory!
871 0           $callback->( -ENOTDIR() );
872             }
873             } else {
874             # path does not exist!
875 0           $callback->( -ENOENT() );
876             }
877              
878 0           return;
879             }
880              
881             sub load {
882             # have to leave @_ alone so caller will get proper $data reference :(
883 0     0 1   my $self = shift;
884 0           my $path = shift;
885              
886             # FIXME fix relative path/sanitize path?
887              
888             # determine if we should be using callback mode
889 0 0         if ( ref( $self ) ne __PACKAGE__ ) {
890 0 0         if ( $self->can( '_load' ) ) {
891 0           $_[1]->( $self->_load( $path, $_[0] ) );
892             } else {
893 0           $_[1]->( -ENOSYS() );
894             }
895 0           return;
896             }
897              
898 0 0         if ( exists $self->_fs->{ $path } ) {
899 0 0         if ( ! S_ISDIR( $self->_fs->{ $path }{'mode'} ) ) {
900             # simply read it all into the buf
901 0           $_[0] = $self->_fs->{ $path }{'data'};
902              
903             # successful load!
904 0           $_[1]->( length( $self->_fs->{ $path }{'data'} ) );
905             } else {
906             # path is a directory!
907 0           $_[1]->( -EISDIR() );
908             }
909             } else {
910             # path does not exist
911 0           $_[1]->( -ENOENT() );
912             }
913              
914 0           return;
915             }
916              
917             sub copy {
918 0     0 1   my( $self, $srcpath, $dstpath, $callback ) = @_;
919              
920             # are we readonly?
921 0 0         if ( $self->readonly ) {
922 0           $callback->( -EROFS() );
923 0           return;
924             }
925              
926             # FIXME fix relative path/sanitize path?
927              
928             # determine if we should be using callback mode
929 0 0         if ( ref( $self ) ne __PACKAGE__ ) {
930 0 0         if ( $self->can( '_copy' ) ) {
931 0           $callback->( $self->_copy( $srcpath, $dstpath ) );
932             } else {
933 0           $callback->( -ENOSYS() );
934             }
935 0           return;
936             }
937              
938 0 0         if ( exists $self->_fs->{ $srcpath } ) {
939 0 0         if ( ! exists $self->_fs->{ $dstpath } ) {
940             # should we add validation to make sure all parents already exist
941             # seems like cp() and friends check themselves, so we don't have to do it...
942              
943             # proceed with the copy!
944 0           $self->_fs->{ $dstpath } = { %{ $self->_fs->{ $srcpath } } };
  0            
945              
946 0           $callback->( 0 );
947             } else {
948             # destination already exists!
949 0           $callback->( -EEXIST() );
950             }
951             } else {
952             # path does not exist
953 0           $callback->( -ENOENT() );
954             }
955              
956 0           return;
957             }
958              
959             sub move {
960 0     0 1   my( $self, $srcpath, $dstpath, $callback ) = @_;
961              
962             # are we readonly?
963 0 0         if ( $self->readonly ) {
964 0           $callback->( -EROFS() );
965 0           return;
966             }
967              
968             # determine if we should be using callback mode
969 0 0         if ( ref( $self ) ne __PACKAGE__ ) {
970 0 0         if ( $self->can( '_move' ) ) {
971 0           $callback->( $self->_move( $srcpath, $dstpath ) );
972             } else {
973 0           $callback->( -ENOSYS() );
974             }
975 0           return;
976             }
977              
978             # to us, move is equivalent to rename
979 0           $self->rename( $srcpath, $dstpath, $callback );
980              
981 0           return;
982             }
983              
984             sub scandir {
985 0     0 1   my( $self, $path, $maxreq, $callback ) = @_;
986              
987             # FIXME fix relative path/sanitize path?
988              
989             # determine if we should be using callback mode
990 0 0         if ( ref( $self ) ne __PACKAGE__ ) {
991 0 0         if ( $self->can( '_scandir' ) ) {
992 0           $callback->( $self->_scandir( $path ) );
993             } else {
994 0           $callback->( -ENOSYS() );
995             }
996 0           return;
997             }
998              
999 0 0         if ( exists $self->_fs->{ $path } ) {
1000 0 0         if ( S_ISDIR( $self->_fs->{ $path }{'mode'} ) ) {
1001             # construct all the data in this directory
1002             # FIXME make this portable!
1003 0 0         my @files = grep { $_ =~ /^$path\/?[^\/]+$/ and ! S_ISDIR( $self->_fs->{ $_ }{'mode'} ) } ( keys %{ $self->_fs } );
  0            
  0            
1004 0           s/^$path\/?// for @files;
1005              
1006 0 0         my @dirs = grep { $_ =~ /^$path\/?[^\/]+$/ and S_ISDIR($self->_fs->{ $_ }{'mode'} ) } ( keys %{ $self->_fs } );
  0            
  0            
1007 0           s/^$path\/?// for @dirs;
1008              
1009             # no need to add "." and ".."
1010              
1011             # return the list!
1012 0           $callback->( \@files, \@dirs );
1013             } else {
1014             # path is not a directory!
1015 0           $callback->( -ENOTDIR() );
1016             }
1017             } else {
1018             # path does not exist!
1019 0           $callback->( -ENOENT() );
1020             }
1021              
1022 0           return;
1023             }
1024              
1025             sub rmtree {
1026 0     0 1   my( $self, $path, $callback ) = @_;
1027              
1028             # are we readonly?
1029 0 0         if ( $self->readonly ) {
1030 0           $callback->( -EROFS() );
1031 0           return;
1032             }
1033              
1034             # FIXME fix relative path/sanitize path?
1035              
1036             # determine if we should be using callback mode
1037 0 0         if ( ref( $self ) ne __PACKAGE__ ) {
1038 0 0         if ( $self->can( '_rmtree' ) ) {
1039 0           $callback->( $self->_rmtree( $path ) );
1040             } else {
1041 0           $callback->( -ENOSYS() );
1042             }
1043 0           return;
1044             }
1045              
1046 0 0         if ( exists $self->_fs->{ $path } ) {
1047 0 0         if ( S_ISDIR( $self->_fs->{ $path }{'mode'} ) ) {
1048             # delete all stuff under this path
1049             # FIXME make this portable!
1050 0           my @entries = grep { $_ =~ /^$path\/?.+$/ } ( keys %{ $self->_fs } );
  0            
  0            
1051 0           foreach my $e ( @entries ) {
1052 0           delete $self->_fs->{ $e };
1053             }
1054              
1055             # return success
1056 0           $callback->( 0 );
1057             } else {
1058             # path is not a directory!
1059 0           $callback->( -ENOTDIR() );
1060             }
1061             } else {
1062             # path does not exist
1063 0           $callback->( -ENOENT() );
1064             }
1065              
1066 0           return;
1067             }
1068              
1069             sub fsync {
1070 0     0 1   my( $self, $fh, $callback ) = @_;
1071              
1072             # not implemented, always return success
1073 0           $callback->( 0 );
1074              
1075 0           return;
1076             }
1077              
1078             sub fdatasync {
1079 0     0 1   my( $self, $fh, $callback ) = @_;
1080              
1081             # not implemented, always return success
1082 0           $callback->( 0 );
1083              
1084 0           return;
1085             }
1086              
1087             1;
1088             __END__