PageRenderTime 60ms CodeModel.GetById 26ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/Hub/Base/FileSystem.pm

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