File Coverage

blib/lib/Filesys/Virtual/Plain.pm
Criterion Covered Total %
statement 21 225 9.3
branch 0 92 0.0
condition 0 12 0.0
subroutine 7 37 18.9
pod 21 21 100.0
total 49 387 12.6


line stmt bran cond sub pod time code
1             package Filesys::Virtual::Plain;
2              
3             ###########################################################################
4             ### Filesys::Virtual::Plain
5             ### L.M.Orchard (deus_x@pobox_com)
6             ### David Davis (xantus@cpan.org)
7             ###
8             ###
9             ### Copyright (c) 1999 Leslie Michael Orchard. All Rights Reserved.
10             ### This module is free software; you can redistribute it and/or
11             ### modify it under the same terms as Perl itself.
12             ###
13             ### Changes Copyright (c) 2003-2004 David Davis and Teknikill Software
14             ###########################################################################
15              
16 1     1   873 use strict;
  1         2  
  1         41  
17 1     1   940 use Filesys::Virtual;
  1         15485  
  1         27  
18 1     1   8 use Carp;
  1         6  
  1         51  
19 1     1   855 use User::pwent;
  1         13601  
  1         8  
20 1     1   876 use User::grent;
  1         2202  
  1         6  
21 1     1   63 use IO::File;
  1         2  
  1         299  
22              
23             our $AUTOLOAD;
24             our $VERSION = '0.10';
25             our @ISA = qw(Filesys::Virtual);
26              
27             our %_fields = (
28             'cwd' => 1,
29             'root_path' => 1,
30             'home_path' => 1,
31             );
32              
33             sub AUTOLOAD {
34 0     0     my $self = shift;
35            
36 0           my $field = $AUTOLOAD;
37 0           $field =~ s/.*:://;
38            
39 0 0         return if $field eq 'DESTROY';
40              
41 0 0         croak("No such property or method '$AUTOLOAD'") if (!$self->_field_exists($field));
42            
43             {
44 1     1   5 no strict "refs";
  1         2  
  1         2854  
  0            
45 0           *{$AUTOLOAD} = sub {
46 0     0     my $self = shift;
47 0 0         return (@_) ? ($self->{$field} = shift) : $self->{$field};
48 0           };
49             }
50            
51 0 0         return (@_) ? ($self->{$field} = shift) : $self->{$field};
52              
53             }
54              
55             =pod
56              
57             =head1 NAME
58              
59             Filesys::Virtual::Plain - A Plain virtual filesystem
60              
61             =head1 SYNOPSIS
62              
63             use Filesys::Virtual::Plain;
64              
65             my $fs = Filesys::Virtual::Plain->new();
66              
67             $fs->login('xantus', 'supersekret');
68              
69             print foreach ($fs->list('/'));
70              
71             =head1 DESCRIPTION
72              
73             This module is used by other modules to provide a pluggable filesystem.
74              
75             =head1 CONSTRUCTOR
76              
77             =head2 new()
78              
79             You can pass the initial cwd, root_path, and home_path as a hash.
80              
81             =head1 METHODS
82              
83             =cut
84              
85             sub new {
86 0     0 1   my $class = shift;
87 0           my $self = {};
88 0           bless($self, $class);
89 0           $self->_init(@_);
90 0           return $self;
91             }
92              
93             sub _init {
94 0     0     my ($self, $params) = @_;
95              
96 0           foreach my $field (keys %_fields) {
97 0 0         next if (!$self->_field_exists($field));
98 0           $self->$field($params->{$field});
99             }
100             }
101              
102             sub _field_exists {
103 0     0     return (defined $_fields{$_[1]});
104             }
105              
106             =pod
107              
108             =head2 login($username, $password, $become)
109              
110             Logs in a user. Returns 0 on failure. If $username is 'anonymous' then it
111             will try to login as 'ftp' with no password. If $become is defined then it
112             will try to change ownership of the process to the uid/gid of the logged in
113             user. BEWARE of the consequences of using $become. login() also sets the
114             uid, gid, home, gids, home_path, and chdir to the users'.
115              
116             =cut
117              
118             sub login {
119 0     0 1   my $self = shift;
120 0           my $username = shift;
121 0           my $password = shift;
122 0           my $become = shift;
123 0           my $pw;
124 0 0         if ($username eq "anonymous") {
125             ### Anonymous login
126 0           $pw = getpwnam("ftp");
127 0 0         unless (defined $pw) {
128 0           return 0;
129             }
130             } else {
131             ### Given username / password
132 0           $pw = getpwnam($username);
133 0 0         unless (defined $pw) {
134 0           return 0;
135             }
136 0           my $cpassword = $pw->passwd();
137 0           my $crpt = crypt($password, $cpassword);
138 0 0         unless ($crpt eq $cpassword) {
139 0           return 0;
140             }
141             }
142 0 0         if (defined $become) {
143 0           $< = $> = $pw->uid();
144 0           $( = $) = $pw->gid();
145             }
146 0           $self->{uid} = $pw->uid();
147 0           $self->{gid} = $pw->gid();
148 0           $self->{home} = $pw->dir();
149 0           $self->{gids}{$pw->gid()} = 1;
150 0           $self->chdir($pw->dir());
151 0           $self->home_path($pw->dir());
152 0           return 1;
153             }
154              
155             =pod
156              
157             =head2 cwd
158              
159             Gets or sets the current directory, assumes / if blank.
160             This is used in conjunction with the root_path for file operations.
161             No actual change directory takes place.
162              
163             =cut
164              
165             sub cwd {
166 0     0 1   my $self = shift;
167            
168 0 0         if (@_) {
169 0           $self->{cwd} = shift;
170             } else {
171 0   0       $self->{cwd} ||= '/';
172             }
173            
174 0           return $self->{cwd};
175             }
176              
177             =pod
178              
179             =head2 root_path($path)
180              
181             Get or set the root path. All file paths are off this and cwd
182             For example:
183              
184             $self->root_path('/home/ftp');
185             $self->cwd('/test');
186             $self->size('testfile.txt');
187              
188             The size command would get the size for file /home/ftp/test/testfile.txt
189             not /test/testfile.txt
190              
191             =cut
192              
193             sub root_path {
194 0     0 1   my ($self) = shift;
195              
196 0 0         if (@_) {
197 0           my $root_path = shift;
198            
199             ### Does the root path end with a '/'? If so, remove it.
200 0 0         $root_path = (substr($root_path, length($root_path)-1, 1) eq '/') ?
201             substr($root_path, 0, length($root_path)-1) : $root_path;
202 0           $self->{root_path} = $root_path;
203             }
204            
205 0           return $self->{root_path};
206             }
207              
208             =pod
209              
210             =head2 chmod($mode,$file)
211              
212             chmod's a file.
213              
214             =cut
215              
216             sub chmod {
217 0     0 1   my ($self, $mode, $fn) = @_;
218 0           $fn = $self->_path_from_root($fn);
219            
220 0 0         return (chmod($mode,$fn)) ? 1 : 0;
221             }
222              
223             =pod
224              
225             =head2 modtime($file)
226              
227             Gets the modification time of a file in YYYYMMDDHHMMSS format.
228              
229             =cut
230              
231             sub modtime {
232 0     0 1   my ($self, $fn) = @_;
233 0           $fn = $self->_path_from_root($fn);
234            
235 0           return (0,"");
236 0           my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
237             $atime,$mtime,$ctime,$blksize,$blocks) = CORE::stat($fn);
238            
239 0           my ($sec, $min, $hr, $dd, $mm, $yy, $wd, $yd, $isdst) =
240 0           localtime($mtime); $yy += 1900; $mm++;
  0            
241            
242 0           return (1,"$yy$mm$dd$hr$min$sec");
243             }
244              
245             =pod
246              
247             =head2 size($file)
248              
249             Gets the size of a file in bytes.
250              
251             =cut
252              
253             sub size {
254 0     0 1   my ($self, $fn) = @_;
255 0           $fn = $self->_path_from_root($fn);
256              
257 0           return (CORE::stat($fn))[7];
258             }
259              
260             =pod
261              
262             =head2 delete($file)
263              
264             Deletes a file, returns 1 or 0 on success or failure.
265              
266             =cut
267              
268             sub delete {
269 0     0 1   my ($self, $fn) = @_;
270 0           $fn = $self->_path_from_root($fn);
271              
272 0 0 0       return ((-e $fn) && (!-d $fn) && (unlink($fn))) ? 1 : 0;
273             }
274              
275             =pod
276              
277             =head2 chdir($dir)
278              
279             Changes the cwd to a new path from root_path.
280             Returns undef on failure or the new path on success.
281              
282             =cut
283              
284             sub chdir {
285 0     0 1   my ($self, $dir) = @_;
286              
287 0           my $new_cwd = $self->_resolve_path($dir);
288 0           my $full_path = $self->root_path().$new_cwd;
289              
290 0 0 0       return ((-e $full_path) && (-d $full_path)) ? $self->cwd($new_cwd) : undef;
291             }
292              
293             =pod
294              
295             =head2 mkdir($dir, $mode)
296              
297             Creats a directory with $mode (defaults to 0755) and chown()'s the directory
298             with the uid and gid. The return value is from mkdir().
299              
300             =cut
301              
302             sub mkdir {
303 0     0 1   my ($self, $dir, $mode) = @_;
304 0           $dir = $self->_path_from_root($dir);
305              
306 0 0         return 2 if (-d $dir);
307            
308 0   0       $mode ||= 0755;
309            
310 0 0         my $ret = (mkdir($dir, $mode)) ? 1 : 0;
311            
312 0 0         if ($ret) {
313 0           chown($self->{uid}, $self->{gid}, $dir);
314             }
315 0           return $ret;
316             }
317              
318             =pod
319              
320             =head2 rmdir($dir)
321              
322             Deletes a directory or file if -d test fails. Returns 1 on success or 0 on
323             failure.
324              
325             =cut
326              
327             sub rmdir {
328 0     0 1   my ($self, $dir) = @_;
329 0           $dir = $self->_path_from_root($dir);
330              
331 0 0         if (-e $dir) {
332 0 0         if (-d $dir) {
333 0 0         return 1 if (rmdir($dir));
334             } else {
335 0 0         return 1 if (unlink($dir));
336             }
337             }
338              
339 0           return 0;
340             }
341              
342             =pod
343              
344             =head2 list($dir)
345              
346             Returns an array of the files in a directory.
347              
348             =cut
349              
350             sub list {
351 0     0 1   my ($self, $dirfile) = @_;
352 0           $dirfile = $self->_path_from_root($dirfile);
353            
354 0           my @ls;
355            
356 0 0         if(-e $dirfile) {
357 0 0         if(!-d $dirfile) {
358             ### This isn't a directory, so derive its short name, and push it.
359 0           my @parts = split(/\//, $dirfile);
360 0           push(@ls, pop @parts);
361             } else {
362             ### Open the directory and get a file list.
363 0           opendir(DIR, $dirfile);
364 0           my @files = readdir(DIR);
365 0           closedir(DIR);
366            
367             ### Process the files...
368 0           @ls = (sort @files);
369             }
370             }
371            
372 0           return @ls;
373             }
374              
375             =pod
376              
377             =head2 list_details($dir)
378              
379             Returns an array of the files in ls format.
380              
381             =cut
382              
383             sub list_details {
384 0     0 1   my ($self, $dirfile) = @_;
385 0           $dirfile = $self->_path_from_root($dirfile);
386            
387 0           my @ls;
388            
389 0 0         if( -e $dirfile ) {
390 0 0         if(! -d $dirfile ) {
391             ### This isn't a directory, so derive its short name, and produce
392             ### an ls line.
393 0           my @parts = split(/\//, $dirfile);
394 0           my $fn = pop @parts;
395 0           push(@ls, $self->_ls_stat($dirfile, $fn));
396             } else {
397             ### Open the directory and get a file list.
398 0           opendir(DIR, $dirfile);
399 0           my @files = readdir(DIR);
400 0           closedir(DIR);
401            
402             ### Make sure the directory path ends in '/'
403 0 0         $dirfile = (substr($dirfile, length($dirfile)-1, 1) eq '/') ? $dirfile : $dirfile.'/';
404            
405             ### Process the files...
406 0           foreach (sort @files) {
407 0           push(@ls, $self->_ls_stat($dirfile.$_, $_));
408             }
409             }
410             }
411            
412 0           return @ls;
413             }
414              
415             =pod
416              
417             =head2 stat($file)
418              
419             Does a normal stat() on a file or directory
420              
421             =cut
422              
423             sub stat {
424 0     0 1   my ($self, $fn) = @_;
425            
426 0           $fn =~ s/\s+/ /g;
427 0           $fn = $self->_path_from_root($fn);
428              
429 0           return CORE::stat($fn);
430             }
431              
432             =pod
433              
434             =head2 test($test,$file)
435              
436             Perform a perl type test on a file and returns the results.
437              
438             For example to perform a -d on a directory.
439              
440             $self->test('d','/testdir');
441              
442             See filetests in perlfunc (commandline: perldoc perlfunc)
443              
444             =cut
445              
446             # -r File is readable by effective uid/gid.
447             # -w File is writable by effective uid/gid.
448             # -x File is executable by effective uid/gid.
449             # -o File is owned by effective uid.
450              
451             # -R File is readable by real uid/gid.
452             # -W File is writable by real uid/gid.
453             # -X File is executable by real uid/gid.
454             # -O File is owned by real uid.
455              
456             # -e File exists.
457             # -z File has zero size.
458             # -s File has nonzero size (returns size).
459              
460             # -f File is a plain file.
461             # -d File is a directory.
462             # -l File is a symbolic link.
463             # -p File is a named pipe (FIFO), or Filehandle is a pipe.
464             # -S File is a socket.
465             # -b File is a block special file.
466             # -c File is a character special file.
467             # -t Filehandle is opened to a tty.
468              
469             # -u File has setuid bit set.
470             # -g File has setgid bit set.
471             # -k File has sticky bit set.
472              
473             # -T File is a text file.
474             # -B File is a binary file (opposite of -T).
475              
476             # -M Age of file in days when script started.
477             # -A Same for access time.
478             # -C Same for inode change time.
479              
480             sub test {
481 0     0 1   my ($self, $test, $fn) = @_;
482              
483 0           $fn = $self->_path_from_root($fn);
484 0           $fn =~ s/'/\\'/g;
485             # NO FUNNY BUSINESS
486 0           $test =~ s/^(.)/$1/;
487            
488 0           my $ret = eval("-$test '$fn'");
489            
490 0 0         return ($@) ? undef : $ret;
491             }
492              
493             =pod
494              
495             =head2 open_read($file,[params])
496              
497             Opens a file with L<IO::File>. Params are passed to open() of IO::File.
498             It returns the file handle on success or undef on failure. This could
499             be technically be used for any sort of open operation. See L<IO::File>'s
500             open method.
501              
502             =cut
503              
504             sub open_read {
505 0     0 1   my ($self, $fin, @opts) = @_;
506 0           $fin =~ s/\s+/ /g;
507 0           $self->{file_path} = $fin = $self->_path_from_root($fin);
508              
509 0           return IO::File->new($fin,@opts);
510             }
511              
512             =pod
513              
514             =head2 close_read($fh)
515              
516             Performs a $fh->close()
517              
518             =cut
519              
520             sub close_read {
521 0     0 1   my ($self, $fh) = @_;
522              
523 0           return $fh->close();
524             }
525              
526             =pod
527              
528             =head2 open_write($fh, $append)
529              
530             Performs an $fh->open(">$file") or $fh->open(">>$file") if $append is defined.
531             Returns the filehandle on success or undef on failure.
532              
533             =cut
534              
535             sub open_write {
536 0     0 1   my ($self, $fin, $append) = @_;
537 0           $fin =~ s/\s+/ /g;
538 0           $self->{file_path} = $fin = $self->_path_from_root($fin);
539            
540 0 0         my $o = (defined($append)) ? '>>' : '>';
541 0           return IO::File->new($o.$fin);
542             }
543              
544             =pod
545              
546             =head2 close_write($fh)
547              
548             Performs a $fh->close()
549              
550             =cut
551              
552             sub close_write {
553 0     0 1   my ($self, $fh) = @_;
554              
555 0           $fh->close();
556            
557 0           return 1;
558             }
559              
560             =pod
561              
562             =head2 seek($fh, $pos, $wence)
563              
564             Performs a $fh->seek($pos, $wence). See L<IO::Seekable>.
565              
566             =cut
567              
568             sub seek {
569 0     0 1   my ($self, $fh, $first, $second) = @_;
570              
571 0           return $fh->seek($first, $second);
572             }
573              
574             =pod
575              
576             =head2 utime($atime, $mtime, @files)
577              
578             Performs a utime() on the file(s). It changes the access time and mod time of
579             those files.
580              
581             =cut
582              
583             sub utime {
584 0     0 1   my ($self, $atime, $mtime, @fn) = @_;
585              
586 0           foreach my $i ( 0 .. $#fn ) {
587 0           $fn[$i] = $self->_path_from_root($fn[$i]);
588             }
589            
590 0           return CORE::utime($atime, $mtime, @fn);
591             }
592              
593              
594             ### Internal methods
595              
596             # Restrict the path to beneath root path
597              
598             sub _path_from_root {
599 0     0     my ($self, $path) = @_;
600              
601 0           return $self->root_path().$self->_resolve_path($path);
602             }
603              
604             # Resolve a path from the current path
605              
606             sub _resolve_path {
607 0     0     my $self = shift;
608 0   0       my $path = shift || '';
609              
610 0           my $cwd = $self->cwd();
611 0           my $path_out = '';
612              
613 0 0         if ($path eq '') {
    0          
614 0           $path_out = $cwd;
615             } elsif ($path eq '/') {
616 0           $path_out = '/';
617             } else {
618 0           my @real_ele = split(/\//, $cwd);
619 0 0         if ($path =~ m/^\//) {
620 0           undef @real_ele;
621             }
622 0           foreach (split(/\//, $path)) {
623 0 0         if ($_ eq '..') {
    0          
    0          
624 0 0         pop(@real_ele) if ($#real_ele);
625             } elsif ($_ eq '.') {
626 0           next;
627             } elsif ($_ eq '~') {
628 0           @real_ele = split(/\//, $self->home_path());
629             } else {
630 0           push(@real_ele, $_);
631             }
632             }
633 0           $path_out = join('/', @real_ele);
634             }
635            
636 0 0         $path_out = (substr($path_out, 0, 1) eq '/') ? $path_out : '/'.$path_out;
637              
638 0           return $path_out;
639             }
640              
641             # Given a file's full path and name, produce a full ls line
642             sub _ls_stat {
643 0     0     my ($self, $full_fn, $fn) = @_;
644            
645 0           my @modes = ("---------", "rwxrwxrwx");
646             # Determine the current year, for time comparisons
647 0           my $curr_year = (localtime())[5]+1900;
648              
649             # Perform stat() on current file.
650 0           my ($mode,$nlink,$uid,$gid,$size,$mtime) = (CORE::stat($full_fn))[2 .. 5,7,9];
651             #my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
652             # $atime,$mtime,$ctime,$blksize,$blocks) = CORE::stat($full_fn);
653            
654             # Format the mod datestamp into the ls format
655 0           my ($day, $mm, $dd, $time, $yr) = (localtime($mtime) =~ m/(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)/);
656            
657             # Get a string of 0's and 1's for the binary file mode/type
658 0           my $bin_str = substr(unpack("B32", pack("N", $mode)), -16);
659            
660             # Produce a permissions map from the file mode
661 0           my $mode_bin = substr($bin_str, -9);
662 0           my $mode_str = '';
663            
664 0           for (my $i=0; $i<9; $i++) {
665 0           $mode_str .= substr($modes[substr($mode_bin, $i, 1)], $i, 1);
666             }
667            
668             # Determine what type of file this is from the file type
669 0           my $type_bin = substr($bin_str, -16, 7);
670 0           my $type_str = '-';
671 0 0         $type_str = 'd' if ($type_bin =~ m/^01/);
672            
673             # Assemble and return the line
674 0 0         return sprintf("%1s%9s %4s %-8s %-8s %8s %3s %2s %5s %s",
675             $type_str, $mode_str, $nlink,
676             $self->_user($uid), $self->_group($gid), $size, $mm, $dd,
677             ($curr_year eq $yr) ? substr($time,0,5) : $yr, $fn);
678             }
679              
680             # Lookup user name by uid
681              
682             {
683             my %user;
684             sub _user {
685 0     0     my ($self, $uid) = @_;
686 0 0         if (!exists($user{$uid})) {
687 0 0         if (defined($uid)) {
688 0           my $obj = getpwuid($uid);
689 0 0         if ($obj) {
690 0           $user{$uid} = $obj->name;
691             } else {
692 0           $user{$uid} = "#$uid";
693             }
694             } else {
695 0           return '#?';
696             }
697             }
698 0           return $user{$uid};
699             }
700             }
701              
702             # Lookup group name by gid
703              
704             {
705             my %group;
706             sub _group {
707 0     0     my ($self, $gid) = @_;
708 0 0         if (!exists($group{$gid})) {
709 0 0         if (defined($gid)) {
710 0           my $obj = getgrgid($gid);
711 0 0         if ($obj) {
712 0           $group{$gid} = $obj->name;
713             } else {
714 0           $group{$gid} = "#$gid";
715             }
716             } else {
717 0           return '#?';
718             }
719             }
720 0           return $group{$gid};
721             }
722             }
723              
724             1;
725              
726             __END__
727              
728             =head1 AUTHOR
729              
730             David Davis, E<lt>xantus@cpan.orgE<gt>, http://teknikill.net/
731              
732             =head1 LICENSE
733              
734             This library is free software; you can redistribute it and/or modify
735             it under the same terms as Perl itself.
736              
737             =head1 SEE ALSO
738              
739             perl(1), L<Filesys::Virtual>, L<Filesys::Virtual::SSH>,
740             L<Filesys::Virtual::DAAP>, L<POE::Component::Server::FTP>,
741             L<Net::DAV::Server>, L<HTTP::Daemon>,
742             http://perladvent.org/2004/20th/
743              
744             =cut