File Coverage

blib/lib/Hub/Base/FileSystem.pm
Criterion Covered Total %
statement 94 456 20.6
branch 20 258 7.7
condition 9 76 11.8
subroutine 20 46 43.4
pod 31 31 100.0
total 174 867 20.0


line stmt bran cond sub pod time code
1             package Hub::Base::FileSystem;
2 1     1   6 use strict;
  1         2  
  1         40  
3 1     1   6 use IO::File;
  1         1  
  1         159  
4 1     1   811 use IO::Dir;
  1         3195  
  1         44  
5 1     1   7 use IO::Handle;
  1         1  
  1         37  
6 1     1   6 use Fcntl qw/:flock/;
  1         2  
  1         145  
7 1     1   1033 use File::Copy qw/copy/;
  1         2670  
  1         71  
8 1     1   7 use Hub qw/:lib/;
  1         2  
  1         11  
9             our $VERSION = '4.00043';
10             our @EXPORT = qw//;
11             our @EXPORT_OK = qw/
12             SEPARATOR
13             META_FILENAME
14             $MODE_TO_MASK
15             fileopen
16             fileclose
17             filetime
18             find
19             cpdir
20             cpfile
21             mvfile
22             rmdirrec
23             rmfile
24             chperm
25             mkdiras
26             getcrown
27             readdir
28             sort_dir_list
29             readfile
30             writefile
31             parsefile
32             pushwp
33             popwp
34             srcpath
35             fixpath
36             secpath
37             getaddr
38             getpath
39             getspec
40             getname
41             getext
42             abspath
43             realpath
44             relpath
45             mkabsdir
46             /;
47              
48             # Win32 modules are installed
49 1     1   589 eval("use Win32::FileSecurity");
  0         0  
  0         0  
50             our $HAS_WIN32 = $@ ? 0 : 1;
51              
52             # Character to use as the file and directory separator
53 1     1   7 use constant SEPARATOR => '/';
  1         2  
  1         95  
54              
55             # Filename for metadata
56 1     1   6 use constant META_FILENAME => '.metadata';
  1         11  
  1         6055  
57              
58             # ------------------------------------------------------------------------------
59             # $MODE_TO_MASK - Translations for Win32::FileSecurity::MakeMask
60             # ------------------------------------------------------------------------------
61              
62             our $MODE_TO_MASK = {
63              
64             '7' => {
65             'FILE' => ["FULL"],
66             'DIR' => ["FULL", "GENERIC_ALL"],
67             },
68              
69             '6' => {
70             'FILE' => ["CHANGE"],
71             'DIR' => ["ADD", "CHANGE", "GENERIC_WRITE", "GENERIC_READ", "GENERIC_EXECUTE"],
72             },
73              
74             '5' => {
75             'FILE' => ["READ", "STANDARD_RIGHTS_EXECUTE"],
76             'DIR' => ["GENERIC_READ", "GENERIC_EXECUTE"],
77             },
78              
79             '4' => {
80             'FILE' => ["READ"],
81             'DIR' => ["GENERIC_READ", "GENERIC_EXECUTE"],
82             },
83              
84             '3' => {
85             'FILE' => ["STANDARD_RIGHTS_WRITE", "STANDARD_RIGHTS_EXECUTE"],
86             'DIR' => ["GENERIC_READ", "GENERIC_EXECUTE"],
87             },
88              
89             '2' => {
90             'FILE' => ["STANDARD_RIGHTS_WRITE"],
91             'DIR' => ["GENERIC_READ", "GENERIC_EXECUTE"],
92             },
93              
94             '1' => {
95             'FILE' => ["STANDARD_RIGHTS_EXECUTE"],
96             'DIR' => ["GENERIC_EXECUTE"],
97             },
98              
99             '0' => {
100             'FILE' => [""],
101             'DIR' => [""],
102             },
103              
104             };
105              
106             #-------------------------------------------------------------------------------
107             # fileopen FILENAME [PARAMS]
108             #
109             # For platforms which don't flock, create a lockfile for a specified
110             # filename. Waits for #winlock_timeout seconds if a lockfile exists (unless
111             # READONLY is specified).
112             #-------------------------------------------------------------------------------
113              
114             sub fileopen {
115 8   50 8 1 22 my $filename = shift || return;
116 8         18 my $readonly = $filename !~ /^>/;
117 8         81 my $handle = IO::File->new($filename);
118 8 50       790 croak "$!: $filename" unless defined $handle;
119 8 50       21 my $flockopr = $readonly ? LOCK_SH : LOCK_EX;
120 8         72 my $flocked = flock($handle,$flockopr);
121 8 50 33     49 if( $@ or not $flocked ) {
122 0         0 my $path = Hub::getpath( $filename );
123 0         0 my $name = Hub::getname( $filename );
124 0         0 my $lock_filename = "$path/.lock-$name";
125 0   0     0 my $timeout = $$Hub{'/conf/timeout/lockfile'} || 1;
126 0         0 $timeout *= 2; # because we only sleep for 1/2 second each loop
127 0         0 for( 0 .. $timeout ) {
128 0 0       0 last unless -e $lock_filename;
129 0 0       0 last if $readonly;
130 0         0 warn( "Waiting for lock on: $filename" );
131 0         0 sleep .5;
132             }#for
133 0 0       0 if( open( LOCKFILE, ">$lock_filename" ) ) {
134 0         0 print LOCKFILE "Lock file";
135 0         0 close LOCKFILE;
136             } else {
137 0 0       0 die( "$!: $lock_filename" ) unless $readonly;
138             }#if
139             }#if
140 8         26 return $handle;
141             }#fileopen
142              
143             #-------------------------------------------------------------------------------
144             # fileclose HANDLE, [FILENAME]
145             #
146             # Unlock and close the file.
147             # Always remove the lockfile for a specified filename.
148             #-------------------------------------------------------------------------------
149              
150             sub fileclose {
151 8     8 1 13 my $handle = shift;
152 8         12 my $filename = shift;
153 8 50       22 if( defined $handle ) {
154 8         59 flock($handle,LOCK_UN);
155 8         90 close $handle;
156             }#if
157 8 50       28 if( $filename ) {
158 8         36 my $path = Hub::getpath( $filename );
159 8         26 my $name = Hub::getname( $filename );
160 8         29 my $lock_filename = "$path/.lock-$name";
161 8 50       264 unlink $lock_filename if -e $lock_filename;
162             }#if
163             }#fileclose
164              
165             # ------------------------------------------------------------------------------
166             # filetime - Return file's timestamp
167             #
168             # filetime LIST, [OPTIONS]
169             #
170             # Where:
171             #
172             # LIST A list of valid path names or file handles
173             # OPTIONS -mtime Return last-modified time (default)
174             # -atime last-accessed time
175             # -ctime creation time
176             # OPTIONS -max Return greatest value (default)
177             # -min least value
178             # ------------------------------------------------------------------------------
179              
180             sub filetime {
181 0     0 1 0 my $opts = Hub::opts( \@_, { mtime => 1, max => 1 } );
182 0         0 my $result = -1;
183 0         0 foreach my $file ( @_ ) {
184 0         0 my $time = -1;
185 0         0 my $fh = new IO::File;
186 0 0       0 if($fh->open($file)) {
187 0         0 my $stats = stat($fh);
188 0 0       0 $$opts{'mtime'} and $time = $stats->mtime();
189 0 0       0 $$opts{'atime'} and $time = $stats->mtime();
190 0 0       0 $$opts{'ctime'} and $time = $stats->mtime();
191 0         0 $fh->close();
192             }#if
193 0 0       0 $result = $$opts{'max'} ? Hub::max( $result, $time ) :
194             Hub::min( $result, $time );
195             }#foreach
196 0         0 return $result;
197             }#filetime
198              
199             # ------------------------------------------------------------------------------
200             # find - Find files on disk
201             # find $directory, [options]
202             #
203             # The directory entries '.' and '..' are always suppressed.
204             #
205             # No sorting is done here, entries appear in directory order with the directory
206             # listing coming before its sub-directory's listings.
207             #
208             # Options:
209             #
210             # -name => \@list|$list Filename patterns to include
211             # -include => \@list|$list Path patterns to include
212             # -exclude => \@list|$list Path patterns to ignore.
213             # -ignore => \@list|$list Path patterns to ignore
214             # -filesonly => 0|1 Omit directory entries from the result
215             # -dirsonly => 0|1 Omit file entries from the result
216             #
217             # Examples:
218             #
219             # # Return the whole mess
220             # find('/var/www/html');
221             #
222             # # Wild-card search
223             # my @list = find('/var/www/html/*.css');
224             #
225             # # Find by filename
226             # my @list = find('/var/www/html', -name => '\.htaccess;\.htpasswd');
227             #
228             # # Ignore these paths
229             # my @list = find('/var/www/html', -ignore => ".bak;.swp");
230             #
231             # # Ignore these paths AND do not recurse into them
232             # my @list = find('/var/www/html', -exclude => "CVS;.svn");
233             #
234             # # Just find these paths
235             # # This would also match a directories named ".gif"!
236             # my @list = find('/var/www/html', -include => ".gif;.jp?g;.png");
237             #
238             # # Omit directory entries from the result
239             # my @list = find('/var/www/html', -filesonly => 1);
240             #
241             # # Omit file entries from the result
242             # my @list = find('/var/www/html', -dirsonly => 1);
243             #
244             # The options:
245             #
246             # -name
247             # -include
248             # -exclude
249             # -ignore
250             #
251             # Can be provided as array references, meaning:
252             #
253             # my @patterns = qw(1024x768.gif 800x600.jpe?g)
254             # my @list = find('/var/www/html', -include => \@patterns);
255             #
256             # is equivelent to:
257             #
258             # my @list = find('/var/www/html', -include => "1024x768.gif;800x600.jpe?g");
259             # ------------------------------------------------------------------------------
260              
261             sub find {
262 0     0 1 0 my $opts = Hub::opts(\@_, {
263             'include' => [],
264             'ignore' => [],
265             'exclude' => [],
266             'name' => [],
267             });
268 0   0     0 my $dir = shift || croak "Provide a directory";
269 0         0 my $opt_hash = shift;
270 0         0 for (qw/name exclude ignore include/) {
271 0 0 0     0 defined $$opts{$_} && ref($$opts{$_}) ne 'ARRAY'
272             and $$opts{$_} = [split /\s*;\s*/, $$opts{$_}];
273             }
274             # Options, for backwards compatablity, can also be provided in a single hash
275 0 0       0 if( defined $opt_hash ) {
276 0 0       0 if( ref($opt_hash) eq 'HASH' ) {
277 0         0 Hub::merge($opts, $opt_hash);
278             } else {
279 0         0 croak "Unknown option: $opt_hash";
280             }#if
281             }#if
282              
283             # Global exludes
284 0 0       0 push @{$$opts{'ignore'}}, split(/\s*;\s*/, $$Hub{'/sys/ENV/GLOBAL_IGNORE'})
  0         0  
285             if defined $$Hub{'/sys/ENV/GLOBAL_IGNORE'};
286 0 0       0 push @{$$opts{'exclude'}}, split(/\s*;\s*/, $$Hub{'/sys/ENV/GLOBAL_EXCLUDE'})
  0         0  
287             if defined $$Hub{'/sys/ENV/GLOBAL_EXCLUDE'};
288              
289             # Single argument such as '/var/www/html/*.html'
290 0 0       0 unless(-d $dir) {
291 0         0 my $path = Hub::getpath($dir);
292 0 0       0 if(-d $path) {
293 0         0 my $name = Hub::getname($dir);
294 0         0 $dir = $path;
295 0         0 $opts->{'include'} = [ $name ];
296 0         0 $opts->{'filesonly'} = 1;
297             }#if
298             }
299              
300             # Translate path patterns like (*.txt or *.*) into regex patterns
301 0         0 foreach my $k (qw/include exclude ignore/) {
302 0         0 map {
303 0         0 $_ =~ s/^\*/.*/;
304 0         0 $_ =~ s/(?
305 0         0 } @{$opts->{$k}};
306             }
307              
308             # Implementation
309 0         0 $dir = Hub::fixpath($dir);
310 0         0 my $found = _find($dir, $opts);
311 0 0       0 return defined $found ? @$found : ();
312              
313             }
314              
315             sub _find {
316 0     0   0 my ($dir, $opts) = @_;
317              
318             # Read directory
319 0         0 my @all = ();
320 0         0 my $d = IO::Dir->new($dir);
321 0 0       0 die "$!: '$dir' in '" . cwd() . "'" unless defined $d;
322 0         0 while (defined($_ = $d->read)) {
323 0 0       0 push @all, $_ unless /^\.+$/;
324             }
325 0         0 undef $d;
326              
327             # Find matches
328 0         0 my $list = ();
329 0         0 my @subdirs = ();
330 0         0 foreach my $name ( @all ) {
331 0         0 my $i = "$dir/$name";
332 0         0 my $ok = 1;
333              
334             # Entire path rule
335 0 0       0 if (@{$opts->{'include'}}) {
  0         0  
336 0         0 $ok = 0;
337 0         0 for (@{$opts->{'include'}}) {
  0         0  
338 0 0       0 if ($i =~ $_) {
339 0         0 $ok = 1;
340 0         0 last;
341             }
342             }
343             }
344              
345             # Filename rule
346 0 0       0 if (@{$opts->{'name'}}) {
  0         0  
347 0         0 $ok = 0;
348 0         0 for (@{$opts->{'name'}}) {
  0         0  
349 0 0       0 if ($name =~ $_) {
350 0         0 $ok = 1;
351 0         0 last;
352             }
353             }
354             }
355              
356             # Exclusion rules
357 0         0 for (@{$opts->{'ignore'}}, @{$opts->{'exclude'}}) {
  0         0  
  0         0  
358 0 0       0 if ($i =~ $_) {
359 0         0 $ok = 0;
360 0         0 last;
361             }
362             }
363              
364             # Looking for just files (or directories?)
365 0 0       0 if( -d $i ) {
366 0 0       0 $ok = 0 if $opts->{'filesonly'};
367             # Regardless, shall we recurse?
368 0         0 my $recurse = 1;
369 0         0 for (@{$opts->{'exclude'}}) {
  0         0  
370 0 0       0 if( $i =~ $_ ) {
371 0         0 $recurse = 0;
372 0         0 last;
373             }
374             }
375 0 0       0 if( $recurse ) {
376 0         0 push @subdirs, $i;
377             }
378             } else {
379 0 0       0 $ok = 0 if $opts->{'dirsonly'};
380             }
381              
382             # If it passed all the rules
383 0 0       0 if( $ok ) {
384 0         0 push @$list, $i;
385             }
386             }
387              
388             # Recurse into subdirectories
389 0         0 foreach my $subdir ( @subdirs ) {
390 0         0 my $found = _find($subdir, $opts);
391 0 0       0 ref($found) eq 'ARRAY' and push @$list, @$found;
392             }
393              
394 0         0 return $list;
395             }#find
396              
397             # ------------------------------------------------------------------------------
398             # cpdir - Copy a directory
399             # cpdir $source_dir, $target_dir, [filters], [permissions], [options]
400             #
401             # B this function does *not* behave like your shell's C command!
402             # It differs in that when the target directory exists, the *contents* of the
403             # source directory are copied. This is done so that the default operation is:
404             #
405             # # don't create /home/$username/newuser!
406             # cpdir('templates/newuser', "/home/$username");
407             #
408             # To get the same behavior as C, use the '-as_subdir' flag.
409             #
410             # Files are only copied when the source file's modified time is newer
411             # (unless the 'force' option is set).
412             #
413             # C: See L
414             #
415             # C: See L
416             #
417             # C:
418             #
419             # -force => 1 # Always perform the copy
420             # -as_subdir => 1 # Copy as a sub-directory of $target
421             # -peers => 1 # The $source and $target are peers (may be
422             # different names)
423             #
424             # -peers and -as_subdir are mutually exclusive
425             #
426             # ------------------------------------------------------------------------------
427              
428             sub cpdir {
429 0     0 1 0 my ($opts, $source_dir, $target_dir, $perms) = Hub::opts(\@_);
430 0 0       0 Hub::merge($opts, $perms) if isa($perms, 'HASH'); # backward compatibility
431 0   0     0 my $target_parent = Hub::getpath($target_dir) || '.';
432 0 0       0 croak "Provide an existing source: $source_dir" unless -d $source_dir;
433 0 0       0 croak "Provide an existing target: $target_parent" unless -d $target_parent;
434 0         0 my $item_count = 0;
435 0 0       0 if ($$opts{'as_subdir'}) {
    0          
436 0 0       0 $target_dir .= SEPARATOR if $target_dir;
437 0         0 $target_dir .= Hub::getname($source_dir);
438 0         0 mkabsdir($target_dir, -opts => $opts);
439             } elsif ($$opts{'peers'}) {
440 0         0 mkabsdir($target_dir, -opts => $opts);
441 0         0 $item_count++;
442             }
443 0         0 my @items = Hub::find($source_dir, -opts => $opts);
444 0         0 foreach my $item (@items) {
445 0         0 my $target = $item;
446 0         0 $target =~ s/^$source_dir/$target_dir/;
447 0 0       0 if( -d $item ) {
448 0 0 0     0 if ((! -d $target) || $opts->{'force'}) {
449 0         0 Hub::mkdiras($target, -opts => $opts);
450             }
451             } else {
452 0         0 Hub::cpfile($item, $target, -opts => $opts);
453             }
454             }
455 0         0 $item_count += @items;
456 0         0 return $item_count;
457             }#cpdir
458              
459             # ------------------------------------------------------------------------------
460             # cpfile - Copy a file and apply permissions and mode
461             #
462             # cpfile $SOURCE, $TARGET, [\%PERMISSIONS], [OPTIONS]
463             #
464             # Where:
465             #
466             # $SOURCE File to be copied
467             # $TARGET Target path (file or directory)
468             # \%PERMISSIONS Permission hash (see Hub::chperm)
469             # OPTIONS -newer Only copy when the source is newer (mtime) than
470             # the target
471             #
472             # See also: L
473             # ------------------------------------------------------------------------------
474              
475             sub cpfile {
476 0     0 1 0 my ($opts, $source, $dest, $perms) = Hub::opts(\@_);
477 0 0       0 Hub::merge($opts, $perms) if isa($perms, 'HASH'); # backward compatibility
478 0         0 my @result = ();
479 0 0       0 foreach my $file (-f $source
    0          
480             ? $source
481             : ref($source) eq 'HASH'
482             ? Hub::find('.', $source)
483             : Hub::find($source)) {
484 0 0       0 return unless -f $file;
485 0         0 my $target = $dest;
486 0 0       0 if(-d $target) {
487 0         0 my $fn = Hub::getname( $file );
488 0         0 $target .= "/$fn";
489             }
490 0         0 my $copy = $$opts{'force'};
491 0 0       0 if( !$copy ) {
492 0         0 my $source_stats = stat( $file );
493 0         0 my $target_stats = stat( $target );
494 0 0 0     0 if( !$target_stats || $source_stats->mtime() > $target_stats->mtime() ) {
495 0         0 $copy = 1;
496             }
497             }
498 0 0       0 if( $copy ) {
499 0         0 my $fpath = Hub::getpath( $target );
500 0         0 Hub::mkabsdir($fpath, -opts => $opts);
501 0 0       0 if( copy( $file, $target ) ) {
502 0         0 Hub::chperm($target, -opts => $opts);
503             } else {
504 0         0 die( "$!: $target" );
505             }
506             }
507 0         0 push @result, $target;
508             }
509 0 0       0 return Hub::sizeof(\@result) == 1
    0          
510             ? shift @result
511             : wantarray
512             ? @result
513             : \@result;
514             }#cpfile
515              
516             # ------------------------------------------------------------------------------
517             # rmfile - Remove file
518             # ------------------------------------------------------------------------------
519              
520             sub rmfile {
521 0     0 1 0 unlink @_;
522             }#rmfile
523              
524             # ------------------------------------------------------------------------------
525             # mvfile - Move (rename) a file
526             # ------------------------------------------------------------------------------
527              
528             sub mvfile {
529 0     0 1 0 my ($f1,$f2) = @_;
530 0         0 rename $f1, $f2;
531 0         0 Hub::touch($f2);
532             }#mvfile
533              
534             # ------------------------------------------------------------------------------
535             # rmdirrec TARGET_DIR
536             #
537             # Recursively remove a directory.
538             # ------------------------------------------------------------------------------
539              
540             sub rmdirrec {
541 0   0 0 1 0 my $dir = shift || die "Provide a directory";
542 0         0 my $fh = IO::Handle->new();
543 0         0 $dir = Hub::abspath( $dir );
544 0 0 0     0 return unless defined $dir && -d $dir;
545 0         0 my @list = ();
546 0 0       0 if( opendir $fh, $dir ) {
547 0         0 my @subdirs = ();
548 0         0 my @all = grep ! /^\.+$/, readdir $fh;
549 0         0 closedir $fh;
550 0         0 foreach my $name ( @all ) {
551 0         0 my $i = "$dir/$name";
552 0 0       0 if( -f $i ) {
    0          
553 0         0 Hub::rmfile( $i );
554             } elsif( -d $i ) {
555 0         0 Hub::rmdirrec( $i );
556             }#if
557             }#foreach
558 0         0 rmdir $dir;
559             }#if
560             }#rmdirrec
561              
562             # ------------------------------------------------------------------------------
563             # chperm - Change permissions of a file or directory
564             # chperm $path, [filters], [permissions], [options]
565             #
566             # options:
567             #
568             # recperms=1 # will recurse if is a directory
569             #
570             # filters: Used when recperms is set. See L.
571             #
572             # permissions:
573             #
574             # uid => Hub::getuid( "username" ), # user id
575             # gid => Hub::getgid( "username" ), # group id
576             # dmode => 0775,
577             # fmode => { # fmode can ref a hash of extensions
578             # '*' => 0644, # '*' is used for unmatched
579             # 'cgi' => 0755, # specific cgi file extension
580             # 'dll' => 'SKIP', # do not update dll files
581             # }
582             # fmode => 0655, # or, fmode can be used for all files
583             #
584             # ------------------------------------------------------------------------------
585              
586             sub chperm {
587 0     0 1 0 my ($opts,$path,$perms) = Hub::opts(\@_, {
588             'recperms' => 0,
589             'fmode' => 0644,
590             });
591 0 0       0 Hub::merge($opts, $perms) if isa($perms, 'HASH'); # backward compatibility
592 0 0       0 my @items = $$opts{'recperms'} ? Hub::find($path, $opts) : $path;
593 0         0 foreach my $target ( @items ) {
594 0 0       0 if (-d $target) {
595 0   0     0 my $mode = $$opts{'dmode'} || 0755;
596 0         0 _chperm($$opts{'uid'}, $$opts{'gid'}, $mode, $target);
597             } else {
598 0         0 my $mode = undef;
599 0 0       0 if (isa($$opts{'fmode'}, 'HASH')) {
600 0         0 my $ext = Hub::getext($target);
601 0 0       0 if( $$opts{'fmode'}->{$ext} ) {
602 0         0 $mode = $$opts{'fmode'}->{$ext};
603             } else {
604 0 0       0 $mode = $$opts{'fmode'}->{'*'} if $$opts{'fmode'}->{'*'};
605             }
606             } else {
607 0         0 $mode = $$opts{'fmode'};
608             }
609 0 0       0 $mode and
610             _chperm($$opts{'uid'}, $$opts{'gid'}, $mode, $target);
611             }
612             }
613             }#chperm
614              
615             # ------------------------------------------------------------------------------
616             # _chperm - Change permission proxy (splits between Win32 and normal routines)
617             # _chperm $user, $group, $mode, @targets
618             #
619             # C<$user> may be either the numeric uid, or the user name
620             #
621             # C<$group> may be either the numeric gid, or the group name
622             #
623             # C<$mode> may be either the octal value (such as 0755) or the string value
624             # (such as '755')
625             #
626             # On win32, default permissions are taken from the configuration file (by
627             # default, '.conf' in the current directory):
628             #
629             # group = /conf/win32/group_name
630             # owner = /conf/win32/owner_name
631             # other = /conf/win32/other_name
632             #
633             # When not specified in the configuration, these values will be
634             #
635             # group = Win32::LoginName
636             # owner = the same as 'other'
637             # other = Everyone
638             # ------------------------------------------------------------------------------
639              
640             sub _chperm {
641 0     0   0 my $owner = shift;
642 0         0 my $group = shift;
643 0         0 my $mode = shift;
644 0         0 foreach my $target ( @_ ) {
645 0 0 0     0 if( $HAS_WIN32 && ($mode ne 'SKIP') ) {
646 0         0 $target = Hub::abspath( $target );
647 0         0 my $mode_str = sprintf( "%o", $mode );
648 0   0     0 my $other = $$Hub{'/conf/win32/other_name'} || 'Everyone';
649 0   0     0 $group ||= $$Hub{'/conf/win32/group_name'} || 'Win32::LoginName';
      0        
650 0   0     0 $owner ||= $$Hub{'/conf/win32/owner_name'};
651 0 0       0 unless($owner) { $owner = $other; $other = ""; }
  0         0  
  0         0  
652 0         0 my $owner_flag = substr( $mode_str, 0, 1 );
653 0         0 my $group_flag = substr( $mode_str, 1, 1 );
654 0         0 my $other_flag = substr( $mode_str, 2, 1 );
655 0         0 my $passed = 1;
656 0 0       0 $owner and $passed &= _chperm_win32( $owner, $owner_flag, $target,
657             "WRITE_OWNER", "WRITE_DAC" );
658 0 0       0 $group and $passed &= _chperm_win32( $group, $group_flag, $target );
659 0 0       0 $other and $passed &= _chperm_win32( $other, $other_flag, $target );
660 0 0       0 _chperm_normal($owner, $group, $mode, $target) unless $passed;
661             } else {
662 0         0 _chperm_normal($owner, $group, $mode, $target);
663             }
664             }
665             }
666              
667             # ------------------------------------------------------------------------------
668             # _chperm_normal - Use chmod and chown to change permissions
669             # _chperm_normal $user, $group, $mode, $target
670             #
671             # See L<_chperm> for $user, $group, and $mode settings
672             # ------------------------------------------------------------------------------
673              
674             sub _chperm_normal {
675 0     0   0 my $owner = shift;
676 0         0 my $group = shift;
677 0         0 my $mode = shift;
678 0         0 my $target = shift;
679             # Change owner first
680 0 0       0 if (defined $owner) {
681 0 0       0 unless (chown Hub::getuid($owner), Hub::getgid($group), $target) {
682 0         0 warn "$!: chown $owner:$group $target";
683             }
684             }
685             # Convert string of octal digits
686 0 0       0 $mode = length(sprintf('%o',$mode)) > 3 ? oct($mode) : $mode;
687 0 0       0 if ($mode ne 'SKIP') {
688 0 0       0 unless (chmod $mode, $target) {
689 0         0 warn "$!: chmod $mode $target";
690             }
691             }
692             }#_chperm_normal
693              
694             # ------------------------------------------------------------------------------
695             # _chperm_win32 - Change permissions on Win32
696             #
697             # On Win32, we still don't "really" change the owner (Anybody know how?)
698             # ------------------------------------------------------------------------------
699              
700             sub _chperm_win32 {
701 0     0   0 my $user = shift;
702 0         0 my $flag = shift;
703 0         0 my $target = shift;
704 0 0       0 my $index = -d $target ? "DIR" : "FILE";
705 0         0 my $mmargs = $MODE_TO_MASK->{$flag}->{$index};
706 0         0 my $retval = 0;
707 0         0 my @mmargs = @_;
708 0 0       0 push @mmargs, @$mmargs if ref($mmargs) eq 'ARRAY';
709 0 0       0 if( @mmargs ) {
710 0         0 $retval = 1;
711 0         0 my $mask = Win32::FileSecurity::MakeMask( @mmargs );
712 0         0 my $privHash = {};
713             # If there isn't an ACL, we receive: Error handling error: 3,
714             # GetFileSecurity
715 0         0 eval( "Win32::FileSecurity::Get( \$target, \$privHash )" );
716 0 0       0 $@ and do { chomp $@; warn( "$target: $@" ); $retval = 0; };
  0         0  
  0         0  
  0         0  
717 0 0       0 return $retval unless $retval;
718 0 0       0 if( $flag ) {
719 0         0 $privHash->{$user} = $mask;
720             } else {
721 0         0 delete $privHash->{$user};
722             }#if
723 0         0 eval( "Win32::FileSecurity::Set( \$target, \$privHash )" );
724 0 0       0 $@ and do { chomp $@; warn( "$target: $@" ); $retval = 0; };
  0         0  
  0         0  
  0         0  
725             }#if
726 0         0 return $retval;
727             }#_chperm_win32
728              
729             # ------------------------------------------------------------------------------
730             # mkdiras - Make a directy with specified permissions
731             # mkdiras $path, [permissions]
732             #
733             # permissions: See L
734             # ------------------------------------------------------------------------------
735              
736             sub mkdiras {
737 0     0 1 0 my ($opts, $path, $perms) = Hub::opts(\@_);
738 0 0       0 croak "Provide a path" unless defined $path;
739 0 0       0 return if -d $path;
740 0 0       0 if (mkdir $path) {
741 0 0       0 Hub::chperm($path, $opts) if %$opts;
742             } else {
743 0         0 croak("$!: $path");
744             }
745             }#mkdiras
746              
747             # ------------------------------------------------------------------------------
748             # getcrown - Return the first line of a file
749             # getcrown $file_path
750             #
751             # Returns empty-string when $file_path does not exist
752             # ------------------------------------------------------------------------------
753              
754             sub getcrown {
755 0 0   0 1 0 my $filepath = shift or croak "Provide a file path";
756 0         0 my $crown = '';
757 0 0       0 if (open FILE, $filepath) {
758 0         0 $crown = ;
759 0         0 close FILE;
760             }
761 0         0 return $crown;
762             }#getcrown
763              
764             # ------------------------------------------------------------------------------
765             # readdir - Read a directory in proper order
766             # readdir $dir
767             # ------------------------------------------------------------------------------
768              
769             sub readdir {
770 1     1 1 2 my $dir = shift;
771 1 50       23 return () unless -d $dir;
772 1 50       35 opendir (DIR, $dir) or die "$!: $dir";
773 1         53 my @list = sort grep {!/^\.+/} readdir DIR;
  17         115  
774 1         14 closedir DIR;
775             # Sort entries
776 1         8 Hub::sort_dir_list($dir, \@list);
777             # my $md_filename = $dir.SEPARATOR.META_FILENAME;
778             # if (-f $md_filename) {
779             # my $md = Hub::mkinst('HashFile', $md_filename);
780             # my $order = $$md{'sort_order'};
781             # if (isa($order, 'ARRAY')) {
782             # my $idx = 0;
783             # my %sort_values = map {$_, $idx++} @$order;
784             # @list = sort {
785             # Hub::compare('<=>', $sort_values{$a}, $sort_values{$b})
786             # } @list;
787             # }
788             # }
789 1         9 return @list;
790             }#readdir
791              
792             # ------------------------------------------------------------------------------
793             # sort_dir_list - Sort the provided directory listing
794             # sort_dir_list $dir, \@listing
795             # ------------------------------------------------------------------------------
796              
797             sub sort_dir_list {
798 9     9 1 34 my ($opts, $dir, $list) = Hub::opts(\@_);
799 9         30 my $md_filename = $dir.SEPARATOR.META_FILENAME;
800 9 50       307 if (-f $md_filename) {
801 0         0 Hub::frefresh($md_filename);
802 0         0 my $md = Hub::mkinst('HashFile', $md_filename);
803             # Sort entries
804 0         0 my $order = $$md{'sort_order'};
805 0 0       0 if (isa($order, 'ARRAY')) {
806 0         0 my $idx = 0;
807 0         0 my %sort_values = map {$_, $idx++} @$order;
  0         0  
808 0         0 @$list = sort {
809 0         0 Hub::sort_compare('<=>', $sort_values{$a}, $sort_values{$b})
810             } @$list;
811             }
812             }
813             }#sort_dir_list
814              
815             # ------------------------------------------------------------------------------
816             # readfile PATH
817             #
818             # Read and return the contents of a file.
819             # ------------------------------------------------------------------------------
820              
821             sub readfile {
822 8   50 8 1 28 my $path = shift || return;
823 8 50       44 my $opts = Hub::opts(\@_) if @_;
824 8         14 local $_;
825 8         11 my @contents = ();
826 8         30 my $fh = Hub::fileopen($path);
827 8 50       23 if( $fh ) {
828 8         1435 @contents = <$fh>;
829 8         75 Hub::fileclose($fh, $path);
830             }#if
831 8 50       2161 defined $$opts{'asa'} and return @contents;
832 0         0 my $contents = '';
833 0         0 map { $contents .= $_ } @contents;
  0         0  
834 0         0 return $contents;
835             }#readfile
836              
837             # ------------------------------------------------------------------------------
838             # writefile - Write $contents to $path
839             # writefile $path, \$contents, [options]
840             # writefile $path, $contents, [options]
841             #
842             # options:
843             #
844             # -mode => 0644 Set/update file's mode
845             # -flags => >|>> Flags used to open the file
846             #
847             # Returns 1 if the file could be openned and written to, otherwise 0.
848             # ------------------------------------------------------------------------------
849              
850             sub writefile {
851 0     0 1 0 my ($opts,$filepath,$contents) = Hub::opts(\@_, {'flags' => '>'});
852 0 0       0 croak "Provide a file" unless $filepath;
853 0 0       0 croak "Provide file contents" unless defined $contents;
854 0         0 my $perms = ();
855 0         0 my $ret = 0;
856 0         0 my $fh = Hub::fileopen("$$opts{'flags'}$filepath");
857 0 0       0 if( $fh ) {
858 0 0       0 print $fh ref($contents) eq 'SCALAR' ? $$contents : $contents;
859 0         0 Hub::fileclose($fh, $filepath);
860 0 0       0 if( defined($$opts{'perms'}) ) {
861 0         0 Hub::chperm($filepath, $$opts{'perms'});
862             }
863 0         0 $ret = 1;
864             }
865 0         0 return $ret;
866             }
867              
868             # ------------------------------------------------------------------------------
869             # parsefile - Populate a file with runtime data.
870             # parsefile $filename, [options]
871             # parsefile $filename, \%data, [\%more_data..], [options]
872             #
873             # parameters:
874             #
875             # $filename File to parse as a template.
876             # \%data Hashref of name/value pairs.
877             #
878             # options:
879             #
880             # -as_ref=1 Return a scalar reference
881             # -alone Do not include configuration and instance values
882             # -inline Update the file on disk!
883             # ------------------------------------------------------------------------------
884              
885             sub parsefile {
886 0     0 1 0 my ($opts) = Hub::opts(\@_, {'as_ref' => 0});
887 0         0 my $file = shift;
888 0 0       0 my @values = @_ ? ( @_ ) : ();
889 0 0       0 push @values, $$Hub{+SEPARATOR} unless $$opts{'alone'};
890 0         0 my $contents = Hub::readfile( $file );
891 0         0 my $parser = Hub::mkinst( 'StandardParser', -template => \$contents,
892             -opts => $opts );
893 0         0 my $results = $parser->populate( @values );
894 0         0 Hub::expect( SCALAR => $results );
895 0 0       0 $$opts{'inline'} and Hub::writefile( $file, $results );
896 0 0       0 return $$opts{'as_ref'} ? $results : $$results;
897             }#parsefile
898              
899             # ------------------------------------------------------------------------------
900             # pushwp - Push path onto working directory stack
901             # ------------------------------------------------------------------------------
902              
903             sub pushwp {
904 0   0 0 1 0 $$Hub{'/sys/PATH'} ||= [];
905 0         0 push @{$$Hub{'/sys/PATH'}}, @_;
  0         0  
906             }#pushwp
907              
908             # ------------------------------------------------------------------------------
909             # popwp - Pop path from working directory stack
910             # ------------------------------------------------------------------------------
911              
912             sub popwp {
913 0     0 1 0 return pop @{$$Hub{'/sys/PATH'}};
  0         0  
914             }#popwp
915              
916             # ------------------------------------------------------------------------------
917             # srcpath - Search the working path for $file
918             # srcpath $file
919             # ------------------------------------------------------------------------------
920              
921             sub srcpath {
922 0   0 0 1 0 my $unknown = shift || return;
923 0 0       0 -e $unknown and return $unknown;
924 0         0 for (
925 0         0 @{$$Hub{'/sys/PATH'}},
926             $$Hub{'/sys/ENV/WORKING_DIR'},
927             $$Hub{'/sys/ENV/BASE_DIR'}
928             ) {
929 0 0 0     0 next unless defined && $_;
930 0         0 my $spec = Hub::fixpath( "$_/$unknown" );
931 0 0       0 if(-e $spec) {
932 0         0 return $spec;
933             }
934             }
935             }#srcpath
936              
937             # ------------------------------------------------------------------------------
938             # secpath - Authorize a path for the runtime access
939             # secpath $path
940             #
941             # Intention is to be able to pass anything to this method and it will only
942             # return a path when it is valid. Being valid means that it resolves to a file
943             # or directory which is at or below the WORKING_DIR.
944             # ------------------------------------------------------------------------------
945              
946             sub secpath {
947 0     0 1 0 my $abspath = Hub::abspath(@_, -must_exit => 0);
948 0 0       0 return defined Hub::getaddr($abspath) ? $abspath : undef;
949             }#secpath
950              
951             #-------------------------------------------------------------------------------
952             # fixpath - Clean up malformed paths (usually due to concatenation logic).
953             # fixpath $path
954             #-------------------------------------------------------------------------------
955             #|test(match) fixpath( "../../../users/newuser/web/bin/../src/screens" );
956             #~ ../../../users/newuser/web/src/screens
957             #~
958             #|test(match) fixpath( "users/newuser/web/" );
959             #~ users/newuser/web
960             #~
961             #|test(match) fixpath( "users/../web/bin/../src" );
962             #~ web/src
963             #~
964             #|test(match) fixpath( "users//newuser" );
965             #~ users/newuser
966             #~
967             #|test(match) fixpath( "users//newuser/./files" );
968             #~ users/newuser/files
969             #~
970             #|test(match) fixpath( "http://site/users//newuser" );
971             #~ http://site/users/newuser
972             #|test(match) fixpath( '/home/hub/build/../../../out/doc/pod' );
973             #~ /out/doc/pod
974             #-------------------------------------------------------------------------------
975              
976             sub fixpath {
977 47   50 47 1 116 my $path = shift || return;
978             # correct solidus
979 47         79 $path =~ s/\\/\//g;
980             # remove empty dirs, ie: // (unless it looks like protocol '://')
981 47         302 $path =~ s/(?
982             # remove pointless dirs, ie: /./
983 47         77 $path =~ s/\/\.\//\//g;
984             # condense relative subdirs
985 47         134 while( $path =~ s/[^\/\.]+\/\.\.\/?//g ) {
986             # remove empty dirs (again)
987 0         0 $path =~ s/(?
988             }#while
989             # remove trailing /
990 47         64 $path =~ s/\/\z//;
991 47         122 return $path;
992             }#fixpath
993              
994             # ------------------------------------------------------------------------------
995             # getaddr - Get the Hub address for a file
996             # getaddr $filename
997             #
998             # C<$filename> may be relative to the running module (see L)
999             #
1000             # For the inverse, see L
1001             # ------------------------------------------------------------------------------
1002              
1003             sub getaddr {
1004 0   0 0 1 0 my $path = Hub::srcpath(@_) || $_[0];
1005 0         0 my $result = ();
1006 0 0       0 return unless defined $path;
1007 0         0 foreach my $dir ($$Hub{'/sys/ENV/WORKING_DIR'}, $$Hub{'/sys/ENV/BASE_DIR'}) {
1008 0 0       0 next unless defined $dir;
1009 0 0       0 $path =~ s#^$dir## and return $path;
1010             }
1011 0         0 return undef;
1012             }#getaddr
1013              
1014             # ------------------------------------------------------------------------------
1015             # getpath - Exract the parent from the given filepath
1016             # ------------------------------------------------------------------------------
1017             #|test(match,/etc) getpath( "/etc/passwd" )
1018             #|test(match,/usr/local) getpath( "/usr/local/bin" )
1019             # ------------------------------------------------------------------------------
1020              
1021             sub getpath {
1022 24   50 24 1 51 my $orig = Hub::fixpath( shift ) || '';
1023 24         99 my ($path) = $orig =~ /(.*)\//;
1024 24   50     99 return $path || '';
1025             }#sub
1026              
1027             # ------------------------------------------------------------------------------
1028             # getspec - Given a path to a file, return (directory, filename, extension)
1029             # getspec $path
1030             # ------------------------------------------------------------------------------
1031              
1032             sub getspec {
1033 0     0 1 0 my $path = shift;
1034 0   0     0 my $name = Hub::getname( $path ) || "";
1035 0   0     0 my $ext = Hub::getext( $path ) || "";
1036 0   0     0 my $dir = Hub::getpath( $path ) || "";
1037 0         0 $name =~ s/\.$ext$//; # return the name w/o extension
1038 0         0 return ($dir,$name,$ext);
1039             }#getspec
1040              
1041             #-------------------------------------------------------------------------------
1042             # getname Return the file name (last element) of given path
1043             # getname $path
1044             # Note, if the given path is a full directory path, the last directory is
1045             # still considerred a filename.
1046             #-------------------------------------------------------------------------------
1047             #|test(match) getname("../../../users/newuser/web/data/p001/batman-small.jpg");
1048             #=batman-small.jpg
1049             #|test(match) getname("../../../users/newuser/web/data/p001");
1050             #=p001
1051             #|test(match) getname("/var/log/*.log");
1052             #=*.log
1053             #-------------------------------------------------------------------------------
1054              
1055             sub getname {
1056 8 50   8 1 22 return unless defined $_[0];
1057 8         11 return pop @{[split(SEPARATOR, $_[0])]};
  8         46  
1058             }
1059              
1060             # ------------------------------------------------------------------------------
1061             # getext - Return the file extension at the given path
1062             # getext $path
1063             # ------------------------------------------------------------------------------
1064             #|test(match) getext( "/foo/bar/filename.ext" )
1065             #=ext
1066             #|test(match) getext( "filename.cgi" )
1067             #=cgi
1068             # ------------------------------------------------------------------------------
1069              
1070             sub getext {
1071 0     0 1 0 my $orig = shift;
1072 0   0     0 my $fn = getname($orig) || '';
1073 0         0 my $tmp = reverse($fn);
1074 0         0 $tmp =~ s/\..*//;
1075 0         0 my $ret = reverse $tmp;
1076 0 0       0 return $ret eq $fn ? '' : $ret;
1077             }#getext
1078              
1079             # ------------------------------------------------------------------------------
1080             # realpath - Resolve the address to it's real file on disk.
1081             # realpath $address
1082             #
1083             # Used to translate our Hub system addresses into real filesystem paths.
1084             # When /foo/bar.txt is really cwd().'/foo/bar.txt', we strip the beginning /.
1085             # When using mounts, return the file's real path.
1086             #
1087             # For the inverse, see L
1088             # ------------------------------------------------------------------------------
1089              
1090             sub realpath {
1091 0     0 1 0 my $real_path = shift;
1092 0 0       0 croak "Provide an address" unless defined $real_path;
1093 0         0 $real_path =~ s/^\///;
1094             # TODO implement mounts
1095 0 0       0 return $real_path ? $real_path : '.';
1096             }#realpath
1097              
1098             #-------------------------------------------------------------------------------
1099             # abspath - Return the absolute path
1100             # abspath $node, [options]
1101             # options:
1102             # -must_exist=0 Allow paths which don't exist
1103             #-------------------------------------------------------------------------------
1104              
1105             sub abspath {
1106 40     40 1 59 my $path = shift; # important to shift (filenames can start with a dash)
1107 40         255 my ($opts) = Hub::opts(\@_, {must_exist => 0,});
1108 40         60 my $result = ();
1109 40 100       78 if ($$opts{'must_exist'}) {
1110 8         37 $result = _find_abspath($path, $$Hub{'/sys/ENV/WORKING_DIR'});
1111 8 50       182 if (! -e $result) {
1112 0         0 $result = _find_abspath($path, $$Hub{'/sys/ENV/BASE_DIR'});
1113             }
1114             # die "$!: $result" unless -e $result;
1115 8 50       129 return undef unless -e $result;
1116             } else {
1117 32         61 $result = _find_abspath($path);
1118             }
1119 40         151 return $result;
1120             }#abspath
1121              
1122             # ------------------------------------------------------------------------------
1123             # _find_abspath - Get the absolute path (may or may not exist)
1124             # _find_abspath $node
1125             # _find_abspath $node $working_dir
1126             # ------------------------------------------------------------------------------
1127              
1128             sub _find_abspath {
1129 40   50 40   82 my $relative_path = shift || return;
1130 40         63 my $base_dir = shift;
1131             # $relative_path =~ s/\\/\//g;
1132 40 100       170 return $relative_path if $relative_path =~ /^\/|^[A-Za-z]:\//;
1133 23   66     123 $base_dir ||= Hub::bestof($$Hub{'/sys/ENV/WORKING_DIR'},Hub::getpath($0));
1134 23 50       91 $base_dir = cwd() unless $base_dir =~ /^\/|^[A-Za-z]:\//;
1135             # $base_dir =~ s/\\/\//g;
1136 23         122 return fixpath("$base_dir/$relative_path");
1137             }#_find_abspath
1138              
1139             # ------------------------------------------------------------------------------
1140             # relpath - Relative path
1141             # relpath $path, $from_dir
1142             # ------------------------------------------------------------------------------
1143             #|test(match,..) relpath("/home/docs", "/home/docs/install");
1144             #|test(match) relpath("/home/src", "/home/docs/install");
1145             #~ ../../src
1146             #|test(match) relpath("/home/docs/README.txt", "/home/docs");
1147             #~ README.txt
1148             #|test(match) relpath("README.txt", "/DEBUG");
1149             #~ README.txt
1150             # ------------------------------------------------------------------------------
1151              
1152             sub relpath {
1153 0   0 0 1   my $path = Hub::fixpath(shift) || '';
1154 0   0       my $from = Hub::fixpath(shift) || '';
1155 0 0         return $path unless $path =~ SEPARATOR;
1156 0           my @from_parts = split SEPARATOR, $from;
1157 0           my @path_parts = split SEPARATOR, $path;
1158 0           my @relpath = ();
1159 0           my $begin_idx = 0;
1160 0           for (my $idx = 0; $idx < @from_parts; $idx++) {
1161 0 0         last unless defined $path_parts[$idx];
1162 0 0         last if $from_parts[$idx] ne $path_parts[$idx];
1163 0           $begin_idx++;
1164             }
1165 0           for (my $idx = $begin_idx; $idx < @from_parts; $idx++) {
1166 0           push @relpath, '..';
1167             }
1168 0           for (my $idx = $begin_idx; $idx < @path_parts; $idx++) {
1169 0           push @relpath, $path_parts[$idx];
1170             }
1171 0           return join SEPARATOR, grep {$_} @relpath;
  0            
1172             }#relpath
1173              
1174             # ------------------------------------------------------------------------------
1175             # mkabsdir - Create the directory specified, including parent directories.
1176             # mkabsdir $dir, [permissions]
1177             # See L
1178             # ------------------------------------------------------------------------------
1179              
1180             sub mkabsdir {
1181 0     0 1   my ($opts, $dir) = Hub::opts(\@_);
1182 0           my $abs_path = _find_abspath($dir);
1183 0 0         return unless $abs_path;
1184 0 0         return $abs_path if -e $abs_path;
1185 0           my $build_path = '';
1186 0           foreach my $part ( split SEPARATOR, $abs_path ) {
1187 0           $build_path .= "$part/";
1188 0 0         -d $build_path and next;
1189 0           Hub::mkdiras($build_path, -opts => $opts);
1190             }
1191 0           return $abs_path;
1192             }#makeAbsoluteDir
1193              
1194             # ------------------------------------------------------------------------------
1195             1;
1196              
1197             __END__