use
5.006;
{
$File::Util::VERSION
=
'4.130500'
;
}
our
$AUTHORITY
=
'cpan:TOMMY'
;
our
@ISA
=
qw( Exporter )
;
our
@EXPORT_OK
=
qw(
NL can_flock ebcdic existent needs_binmode
SL strip_path is_readable is_writable valid_filename
OS bitmask return_path file_type escape_filename
is_bin created last_access last_changed last_modified
isbin split_path atomize_path diagnostic abort_depth
size can_read can_write read_limit
)
;
our
%EXPORT_TAGS
= (
all
=> [
@EXPORT_OK
],
diag
=> [ ] );
our
$WANT_DIAGNOSTICS
= 0;
use
subs
qw( can_read can_write isbin readlimit )
;
sub
new {
my
$this
= { };
bless
$this
,
shift
@_
;
my
$in
=
$this
->_parse_in(
@_
) || { };
$this
->{opts} =
$in
|| { };
$this
->{opts}->{onfail} ||=
'die'
;
$USE_FLOCK
=
$in
->{use_flock}
if
exists
$in
->{use_flock}
&&
defined
$in
->{use_flock};
$this
->{opts}->{use_flock} =
$USE_FLOCK
;
$WANT_DIAGNOSTICS
=
$in
->{diag}
if
exists
$in
->{diag}
&&
defined
$in
->{diag};
$this
->{opts}->{diag} =
$WANT_DIAGNOSTICS
;
$in
->{read_limit} =
defined
$in
->{read_limit}
?
$in
->{read_limit}
:
defined
$in
->{readlimit}
?
$in
->{readlimit}
:
undef
;
delete
$in
->{readlimit};
delete
$in
->{read_limit}
if
!
defined
$in
->{read_limit};
$READ_LIMIT
=
$in
->{read_limit}
if
exists
$in
->{read_limit}
&&
defined
$in
->{read_limit}
&&
$in
->{read_limit} !~ /\D/;
$this
->{opts}->{read_limit} =
$READ_LIMIT
;
$ABORT_DEPTH
=
$in
->{abort_depth}
if
exists
$in
->{abort_depth}
&&
defined
$in
->{abort_depth}
&&
$in
->{abort_depth} !~ /\D/;
$this
->{opts}->{abort_depth} =
$ABORT_DEPTH
;
return
$this
;
}
sub
import
{
my
(
$class
,
@wanted_symbols
) =
@_
;
++
$WANT_DIAGNOSTICS
if
grep
{ /(?<!!):diag/ }
@wanted_symbols
;
$class
->export_to_level( 1,
@_
);
}
sub
list_dir {
my
$this
=
shift
@_
;
my
$opts
=
$this
->_remove_opts( \
@_
);
my
$dir
=
shift
@_
;
my
$path
=
$dir
;
my
(
@dirs
,
@files
,
@items
);
my
$abort_depth
=
defined
$opts
->{abort_depth}
?
$opts
->{abort_depth}
:
defined
$this
->{opts}->{abort_depth}
?
$this
->{opts}->{abort_depth}
:
$ABORT_DEPTH
;
return
$this
->_throw(
'no input'
=> {
meth
=>
'list_dir'
,
missing
=>
'a directory name'
,
opts
=>
$opts
,
}
)
unless
defined
$dir
&&
length
$dir
;
$path
=~ s/[\/\\:]+$//o;
$opts
->{no_fsdots} = 1
if
$opts
->{recurse};
return
$this
->_as_tree(
$dir
=>
$opts
)
if
$opts
->{ as_tree };
my
$recursing
= 0;
return
$this
->_throw(
'no such file'
=> {
opts
=>
$opts
,
filename
=>
$dir
} )
unless
-e
$dir
;
unless
(
length
$dir
== 1 ||
$dir
=~ /^
$WINROOT
$/o ) {
$dir
=~ s/[\/\\:]+$//o;
}
return
$this
->_throw (
'called opendir on a file'
=> {
filename
=>
$dir
,
opts
=>
$opts
,
}
)
unless
-d
$dir
;
$recursing
= 1
if
$opts
->{follow} ||
$opts
->{recurse};
$opts
->{_recursion} = {
_depth
=> 0,
_base
=>
$dir
,
_inodes
=> {},
}
unless
defined
$opts
->{_recursion};
{
my
(
$dev
,
$inode
) = (
lstat
$dir
)[0,1];
next
unless
$inode
;
my
$dir_ident
=
$dev
.
'_'
.
$inode
;
warn
sprintf
qq(*WARNING! Filesystem loop detected at %s, dev %s, inode %s\n)
,
$dir
,
$dev
,
$inode
and
return
( () )
if
exists
$opts
->{_recursion}{_inodes}{
$dir_ident
};
$opts
->{_recursion}{_inodes}{
$dir_ident
} =
undef
;
}
my
(
$trailing_dirs
) =
$dir
=~
/^ \Q
$opts
->{_recursion}{_base}\E [\/\\:] (.+)/x;
if
(
defined
$trailing_dirs
&&
length
$trailing_dirs
) {
my
$depth
= @{[
split
/[\/\\:]+/,
$trailing_dirs
]};
$opts
->{_recursion}{_depth} =
$depth
|| 0;
}
return
( () )
if
$opts
->{max_depth} &&
$opts
->{_recursion}{_depth} >=
$opts
->{max_depth};
return
$this
->_throw(
'abort_depth exceeded'
=> {
meth
=>
'list_dir'
,
abort_depth
=>
$abort_depth
,
opts
=>
$opts
,
}
)
if
$opts
->{_recursion}{_depth} >=
$abort_depth
&&
$abort_depth
!= 0;
opendir
my
$dir_fh
,
$dir
or
return
$this
->_throw
(
'bad opendir'
=> {
dirname
=>
$dir
,
exception
=> $!,
opts
=>
$opts
,
}
);
rewinddir
$dir_fh
;
@files
=
defined
$opts
->{pattern}
?
grep
/
$opts
->{pattern}/,
readdir
$dir_fh
:
readdir
$dir_fh
;
@files
=
defined
$opts
->{rpattern}
?
grep
{ -d
$path
. SL .
$_
|| /
$opts
->{rpattern}/ }
@files
:
@files
;
closedir
$dir_fh
or
return
$this
->_throw(
'close dir'
=> {
dir
=>
$dir
,
exception
=> $!,
opts
=>
$opts
,
}
);
@files
=
grep
{ !/
$FSDOTS
/o }
@files
if
$opts
->{no_fsdots};
@files
= _list_dir_matching(
$opts
,
$path
, \
@files
)
if
grep
{ /match/ }
keys
%$opts
;
for
my
$file
(
@files
) {
warn
qq(ERROR: Got a zero-length filename while reading "$dir"\n)
and
next
unless
length
$file
;
my
$listing
= (
$opts
->{with_paths} ||
$recursing
)
?
$path
. SL .
$file
:
$file
;
if
( -d
$path
. SL .
$file
&& !-l
$path
. SL .
$file
) {
push
@dirs
,
$listing
}
else
{
push
@items
,
$listing
}
}
if
(
my
$cb
=
$opts
->{callback} ) {
$this
->throw(
qq(callback "$cb" not a coderef)
,
$opts
)
unless
ref
$cb
eq
'CODE'
;
$cb
->(
$dir
, \
@dirs
, \
@items
,
$opts
->{_recursion}{_depth} );
}
if
(
my
$cb
=
$opts
->{d_callback} ) {
$this
->throw(
qq(d_callback "$cb" not a coderef)
,
$opts
)
unless
ref
$cb
eq
'CODE'
;
$cb
->(
$dir
, \
@dirs
,
$opts
->{_recursion}{_depth} );
}
if
(
my
$cb
=
$opts
->{f_callback} ) {
$this
->throw(
qq(f_callback "$cb" not a coderef)
,
$opts
)
unless
ref
$cb
eq
'CODE'
;
$cb
->(
$dir
, \
@items
,
$opts
->{_recursion}{_depth} );
}
if
(
$recursing
) {
@dirs
=
grep
{ strip_path(
$_
) !~ /
$FSDOTS
/ }
@dirs
;
for
my
$subdir
(
@dirs
) {
my
$recurse_opts
= {
as_ref
=> 1,
with_paths
=> 1,
recursing
=> 1,
no_fsdots
=> 1,
abort_depth
=>
$abort_depth
,
max_depth
=>
$opts
->{max_depth},
onfail
=>
$opts
->{onfail},
diag
=>
$opts
->{diag},
rpattern
=>
$opts
->{rpattern},
files_match
=>
$opts
->{files_match},
dirs_match
=>
$opts
->{dirs_match},
parent_matches
=>
$opts
->{parent_matches},
path_matches
=>
$opts
->{path_matches},
callback
=>
$opts
->{callback},
d_callback
=>
$opts
->{d_callback},
f_callback
=>
$opts
->{f_callback},
_recursion
=>
$opts
->{_recursion},
_files_match_and
=>
$opts
->{_files_match_and},
_files_match_or
=>
$opts
->{_files_match_or},
_dirs_match_and
=>
$opts
->{_dirs_match_and},
_dirs_match_or
=>
$opts
->{_dirs_match_or},
_parent_matches_and
=>
$opts
->{_parent_matches_and},
_parent_matches_or
=>
$opts
->{_parent_matches_or},
_path_matches_and
=>
$opts
->{_path_matches_and},
_path_matches_or
=>
$opts
->{_path_matches_or},
};
my
(
$dirs_ref
,
$files_ref
) =
$this
->list_dir(
$subdir
,
$recurse_opts
);
push
@dirs
,
@$dirs_ref
if
ref
$dirs_ref
&&
ref
$dirs_ref
eq
'ARRAY'
;
push
@items
,
@$files_ref
if
ref
$files_ref
&&
ref
$files_ref
eq
'ARRAY'
;
}
}
if
(
!
$opts
->{recursing} &&
(
$opts
->{path_matches} ||
$opts
->{parent_matches}
)
) {
@dirs
= _list_dir_lastround_dirmatch(
$opts
, \
@dirs
);
}
if
(
$opts
->{sl_after_dirs} ) {
$_
.= SL
for
grep
{ !/
$FSDOTS
/o }
@dirs
;
}
my
$return_dirs
= [];
my
$return_files
= [];
if
(
$opts
->{ignore_case} ) {
$return_dirs
= [
sort
{
uc
$a
cmp
uc
$b
}
@dirs
];
$return_files
= [
sort
{
uc
$a
cmp
uc
$b
}
@items
];
}
else
{
$return_dirs
= [
sort
{
$a
cmp
$b
}
@dirs
];
$return_files
= [
sort
{
$a
cmp
$b
}
@items
];
}
return
scalar
@$return_dirs
if
$opts
->{dirs_only} &&
$opts
->{count_only};
return
scalar
@$return_files
if
$opts
->{files_only} &&
$opts
->{count_only};
return
scalar
@$return_dirs
+
scalar
@$return_files
if
$opts
->{count_only};
return
$return_dirs
,
$return_files
if
$opts
->{as_ref};
$return_dirs
= [
$return_dirs
]
if
$opts
->{dirs_as_ref};
$return_files
= [
$return_files
]
if
$opts
->{files_as_ref};
return
@$return_dirs
if
$opts
->{dirs_only};
return
@$return_files
if
$opts
->{files_only};
return
@$return_dirs
,
@$return_files
;
}
sub
_list_dir_matching {
my
(
$opts
,
$path
,
$files
) =
@_
;
my
@qualified_files
=
map
{
$path
. SL .
$_
}
splice
@$files
, 0;
my
@qualified_dirs
=
grep
{ !-l
$_
}
grep
{ -d
$_
}
@qualified_files
;
my
%dirs_only
;
@dirs_only
{
@qualified_dirs
} =
@qualified_dirs
;
@qualified_files
=
grep
{ !
exists
$dirs_only
{
$_
} }
@qualified_files
;
my
@files_match
=
map
{ (
$_
) =~ /^.*[\/\\:](.+)/o }
@qualified_files
;
my
@dirs_match
=
map
{ (
$_
) =~ /^.*[\/\\:](.+)/o }
@qualified_dirs
;
undef
%dirs_only
;
undef
@qualified_files
;
undef
@qualified_dirs
;
{
$opts
->{_files_match_and} = []
unless
defined
$opts
->{_files_match_and};
$opts
->{_files_match_and} =
[ _gather_and_patterns(
$opts
->{files_match} ) ]
unless
@{
$opts
->{_files_match_and} };
$opts
->{_files_match_or} = []
unless
defined
$opts
->{_files_match_or};
$opts
->{_files_match_or} =
[ _gather_or_patterns(
$opts
->{files_match} ) ]
unless
@{
$opts
->{_files_match_and} };
$opts
->{_dirs_match_and} = []
unless
defined
$opts
->{_dirs_match_and};
$opts
->{_dirs_match_and} =
[ _gather_and_patterns(
$opts
->{dirs_match} ) ]
unless
@{
$opts
->{_dirs_match_and} };
$opts
->{_dirs_match_or} = []
unless
defined
$opts
->{_dirs_match_or};
$opts
->{_dirs_match_or} =
[ _gather_or_patterns(
$opts
->{dirs_match} ) ]
unless
@{
$opts
->{_dirs_match_and} };
$opts
->{_parent_matches_and} = []
unless
defined
$opts
->{_parent_matches_and};
$opts
->{_parent_matches_and} =
[ _gather_and_patterns(
$opts
->{parent_matches} ) ]
unless
@{
$opts
->{_parent_matches_and} };
$opts
->{_parent_matches_or} = []
unless
defined
$opts
->{_parent_matches_or};
$opts
->{_parent_matches_or} =
[ _gather_or_patterns(
$opts
->{parent_matches} ) ]
unless
@{
$opts
->{_parent_matches_and} };
$opts
->{_path_matches_and} = []
unless
defined
$opts
->{_path_matches_and};
$opts
->{_path_matches_and} =
[ _gather_and_patterns(
$opts
->{path_matches} ) ]
unless
@{
$opts
->{_path_matches_and} };
$opts
->{_path_matches_or} = []
unless
defined
$opts
->{_path_matches_or};
$opts
->{_path_matches_or} =
[ _gather_or_patterns(
$opts
->{path_matches} ) ]
unless
@{
$opts
->{_path_matches_and} };
}
for
my
$pattern
( @{
$opts
->{_files_match_and} } ) {
@files_match
=
grep
{ /
$pattern
/ }
@files_match
;
}
@files_match
= _match_and(
$opts
->{_files_match_and}, \
@files_match
)
if
@{
$opts
->{_files_match_and} };
@files_match
= _match_or(
$opts
->{_files_match_or}, \
@files_match
)
if
@{
$opts
->{_files_match_or} };
@dirs_match
= _match_and(
$opts
->{_dirs_match_and}, \
@dirs_match
)
if
@{
$opts
->{_dirs_match_and} };
@dirs_match
= _match_or(
$opts
->{_dirs_match_or}, \
@dirs_match
)
if
@{
$opts
->{_dirs_match_or} };
if
(
$opts
->{files_match} &&
$opts
->{dirs_match} ) {
@files_match
= ( )
unless
_match_and(
$opts
->{_dirs_match_and}, [ strip_path(
$path
) ] );
}
if
(
$opts
->{parent_matches} ) {
if
( @{
$opts
->{_parent_matches_and} } ) {
@files_match
= ( )
unless
_match_and(
$opts
->{_parent_matches_and}, [ strip_path(
$path
) ]
);
}
elsif
( @{
$opts
->{_parent_matches_or} } ) {
@files_match
= ( )
unless
_match_or(
$opts
->{_parent_matches_or}, [ strip_path(
$path
) ]
);
}
}
if
(
$opts
->{path_matches} ) {
if
( @{
$opts
->{_path_matches_and} } ) {
@files_match
= ( )
unless
_match_and(
$opts
->{_path_matches_and}, [
$path
] );
}
elsif
( @{
$opts
->{_path_matches_or} } ) {
@files_match
= ( )
unless
_match_or(
$opts
->{_path_matches_or}, [
$path
] );
}
}
return
(
@dirs_match
,
@files_match
);
}
sub
_list_dir_lastround_dirmatch {
my
(
$opts
,
$dirs
) =
@_
;
my
@return_dirs
;
if
(
$opts
->{parent_matches} ) {
my
%return_dirs
;
if
( @{
$opts
->{_parent_matches_and} } ) {
for
my
$qfd_dir
(
@$dirs
) {
my
(
$root
,
$in_path
) = atomize_path(
$qfd_dir
);
$in_path
=
$root
.
$in_path
if
$root
;
$return_dirs
{
$in_path
} =
$in_path
if
_match_and(
$opts
->{_parent_matches_and}, [ strip_path(
$in_path
) ] );
}
}
elsif
( @{
$opts
->{_parent_matches_or} } ) {
for
my
$qfd_dir
(
@$dirs
) {
my
(
$root
,
$in_path
) = atomize_path(
$qfd_dir
);
$in_path
=
$root
.
$in_path
if
$root
;
$return_dirs
{
$in_path
} =
$in_path
if
_match_or(
$opts
->{_parent_matches_or}, [ strip_path(
$in_path
) ] );
}
}
push
@return_dirs
,
keys
%return_dirs
;
}
if
(
$opts
->{path_matches} ) {
my
%return_dirs
;
if
( @{
$opts
->{_path_matches_and} } ) {
for
my
$qfd_dir
(
@$dirs
) {
my
(
$root
,
$in_path
) = atomize_path(
$qfd_dir
);
$in_path
=
$root
.
$in_path
if
$root
;
$return_dirs
{
$in_path
} =
$in_path
if
_match_and(
$opts
->{_path_matches_and}, [
$in_path
] );
$return_dirs
{
$qfd_dir
} =
$qfd_dir
if
_match_and(
$opts
->{_path_matches_and}, [
$qfd_dir
] );
}
}
elsif
( @{
$opts
->{_path_matches_or} } ) {
for
my
$qfd_dir
(
@$dirs
) {
my
(
$root
,
$in_path
) = atomize_path(
$qfd_dir
);
$in_path
=
$root
.
$in_path
if
$root
;
$return_dirs
{
$in_path
} =
$in_path
if
_match_or(
$opts
->{_path_matches_or}, [
$in_path
] );
$return_dirs
{
$qfd_dir
} =
$qfd_dir
if
_match_or(
$opts
->{_path_matches_or}, [
$qfd_dir
] );
}
}
push
@return_dirs
,
keys
%return_dirs
;
}
return
@return_dirs
;
}
sub
_gather_and_patterns {
my
$pattern_ref
=
shift
@_
;
return
defined
$pattern_ref
&&
ref
$pattern_ref
eq
'HASH'
&&
defined
$pattern_ref
->{and} &&
ref
$pattern_ref
->{and} eq
'ARRAY'
? @{
$pattern_ref
->{and} }
:
defined
$pattern_ref
&&
ref
$pattern_ref
eq
'Regexp'
? (
$pattern_ref
)
: ( );
}
sub
_gather_or_patterns {
my
$pattern_ref
=
shift
@_
;
return
defined
$pattern_ref
&&
ref
$pattern_ref
eq
'HASH'
&&
defined
$pattern_ref
->{or} &&
ref
$pattern_ref
->{or} eq
'ARRAY'
? @{
$pattern_ref
->{or} }
: ( );
}
sub
_match_and {
my
(
$patterns
,
$items
) =
@_
;
for
my
$pattern
(
@$patterns
) {
@$items
=
grep
{ /
$pattern
/ }
@$items
;
}
return
@$items
;
}
sub
_match_or {
my
(
$patterns
,
$items
) =
@_
;
my
$or_pattern
;
for
my
$pattern
(
@$patterns
) {
$or_pattern
=
$or_pattern
?
qr/$pattern|$or_pattern/
:
$pattern
;
}
@$items
=
grep
{ /
$or_pattern
/ }
@$items
;
return
@$items
;
}
sub
_as_tree {
my
$this
=
shift
@_
;
my
$opts
=
$this
->_remove_opts( \
@_
);
my
$dir
=
shift
@_
;
my
$tree
= {};
my
$treeify
=
sub
{
my
(
$dirname
,
$subdirs
,
$files
) =
@_
;
my
(
$root
,
$branch
,
$leaf
) = atomize_path(
$dirname
);
my
@path_dirs
=
split
/
$DIRSPLIT
/o,
$branch
;
my
@lineage
= (
@path_dirs
,
$leaf
);
unshift
@lineage
,
$root
if
$root
;
my
$ancestory
=
$tree
;
for
(
my
$i
= 0;
$i
<
@lineage
;
$i
++ )
{
my
$self
=
$lineage
[
$i
];
my
$parent
=
$i
> 0 ?
$i
- 1 :
undef
;
if
(
defined
$parent
)
{
my
@predecessors
=
@lineage
[ 0 ..
$parent
];
shift
@predecessors
if
@predecessors
> 1 &&
$predecessors
[0] eq SL;
$parent
=
join
SL,
@predecessors
;
$parent
=
$root
.
$parent
if
$root
&&
$parent
ne
$root
;
}
$ancestory
->{
$self
} ||= { };
unless
(
exists
$opts
->{dirmeta} &&
defined
$opts
->{dirmeta} &&
$opts
->{dirmeta} == 0
) {
$ancestory
->{
$self
}{ _DIR_PARENT_ } =
$parent
;
$ancestory
->{
$self
}{ _DIR_SELF_ } =
!
defined
$parent
?
$self
:
$parent
eq
$root
?
$parent
.
$self
:
$parent
. SL .
$self
;
}
$ancestory
=
$ancestory
->{
$self
};
}
my
$parent
=
$ancestory
;
for
my
$subdir
(
@$subdirs
)
{
$parent
->{ strip_path(
$subdir
) } ||= { };
}
for
my
$file
(
@$files
)
{
$parent
->{ strip_path(
$file
) } =
$file
;
}
};
$this
->list_dir(
$dir
=> {
callback
=>
$treeify
,
recurse
=>
$opts
->{recurse},
files_match
=>
$opts
->{files_match},
dirs_match
=>
$opts
->{dirs_match},
parent_matches
=>
$opts
->{parent_matches},
path_matches
=>
$opts
->{path_matches},
pattern
=>
$opts
->{pattern},
rpattern
=>
$opts
->{rpattern},
}
);
return
$tree
;
}
sub
_dropdots {
my
$this
=
shift
@_
;
my
$opts
=
$this
->_remove_opts( \
@_
);
my
@copy
=
@_
;
my
@out
= ();
my
@dots
= ();
my
$gottadot
= 0;
while
(
@copy
) {
if
(
$gottadot
== 2 ) {
push
@out
,
@copy
and
last
}
my
$dir_item
=
shift
@copy
;
if
(
$dir_item
=~ /
$FSDOTS
/ ) {
++
$gottadot
;
push
@dots
,
$dir_item
;
next
;
}
push
@out
,
$dir_item
;
}
return
( \
@dots
,
@out
)
if
$opts
->{save_dots};
return
@out
;
}
sub
load_file {
my
$this
=
shift
@_
;
my
$in
=
$this
->_parse_in(
@_
);
my
@dirs
= ();
my
$blocksize
= 1024;
my
$fh_passed
= 0;
my
$fh
;
my
(
$file
,
$root
,
$path
,
$clean_name
,
$content
,
$mode
) =
(
''
,
''
,
''
,
''
,
''
,
'read'
);
$in
->{read_limit} =
defined
$in
->{read_limit}
?
$in
->{read_limit}
:
defined
$in
->{readlimit}
?
$in
->{readlimit}
:
undef
;
delete
$in
->{readlimit};
delete
$in
->{read_limit}
if
!
defined
$in
->{read_limit};
my
$read_limit
=
defined
$in
->{read_limit}
?
$in
->{read_limit}
:
defined
$this
->{opts}->{read_limit}
?
$this
->{opts}->{read_limit}
:
defined
$READ_LIMIT
?
$READ_LIMIT
: 0;
return
$this
->_throw(
'bad read_limit'
=> {
opts
=>
$in
,
bad
=>
$read_limit
}
)
if
$read_limit
=~ /\D/;
$in
->{FH} =
$in
->{file_handle}
if
defined
$in
->{file_handle};
if
( !
defined
$in
->{FH} ) {
$file
=
defined
$in
->{file}
?
$in
->{file}
:
defined
$in
->{filename}
?
$in
->{filename}
:
shift
@_
||
''
;
return
$this
->_throw(
'no input'
,
{
meth
=>
'load_file'
,
missing
=>
'a file name or file handle reference'
,
opts
=>
$in
,
}
)
unless
length
$file
;
(
$root
,
$path
,
$file
) = atomize_path(
$file
);
@dirs
=
split
/
$DIRSPLIT
/,
$path
;
unshift
@dirs
,
$root
if
$root
;
if
( !
length
$root
&& !
length
$path
) {
$path
=
'.'
. SL;
}
else
{
$path
.= SL;
}
$clean_name
=
$root
.
$path
.
$file
;
}
else
{
if
(
ref
$in
->{FH} eq
'GLOB'
) {
$fh_passed
++;
}
else
{
return
$this
->_throw(
'no input'
,
{
meth
=>
'load_file'
,
missing
=>
'a true file handle reference (not a string)'
,
opts
=>
$in
,
}
);
}
}
if
(
$fh_passed
) {
my
$buffer
= 0;
my
$bytes_read
= 0;
$fh
=
$in
->{FH};
while
( <
$fh
> ) {
if
(
$buffer
<
$read_limit
) {
$bytes_read
=
read
(
$fh
,
$content
,
$blocksize
);
$buffer
+=
$bytes_read
;
}
else
{
return
$this
->_throw(
'read_limit exceeded'
,
{
filename
=>
'<filehandle>'
,
size
=>
qq{[truncated at $bytes_read]}
,
read_limit
=>
$read_limit
,
opts
=>
$in
,
}
);
}
}
return
split
/
$NL
|\r|\n/o,
$content
if
$in
->{as_list};
return
$content
;
}
return
$this
->_throw(
'no such file'
,
{
filename
=>
$clean_name
,
opts
=>
$in
,
}
)
unless
-e
$clean_name
;
return
$this
->_throw(
'cant dread'
,
{
filename
=>
$clean_name
,
dirname
=>
$root
.
$path
,
opts
=>
$in
,
}
)
unless
-r
$root
.
$path
;
return
$this
->_throw(
'cant fread'
,
{
filename
=>
$clean_name
,
dirname
=>
$root
.
$path
,
opts
=>
$in
,
}
)
unless
-r
$clean_name
;
return
$this
->_throw(
'called open on a dir'
,
{
filename
=>
$clean_name
,
opts
=>
$in
,
}
)
if
-d
$clean_name
;
my
$fsize
= -s
$clean_name
;
return
$this
->_throw(
'read_limit exceeded'
,
{
filename
=>
$clean_name
,
size
=>
$fsize
,
opts
=>
$in
,
read_limit
=>
$read_limit
,
}
)
if
$fsize
>
$read_limit
;
local
$/;
if
(
$in
->{no_lock} ||
$this
->{opts}->{no_lock} ||
!
$this
->use_flock()
) {
open
$fh
,
'<'
,
$clean_name
or
return
$this
->_throw(
'bad open'
,
{
filename
=>
$clean_name
,
mode
=>
$mode
,
exception
=> $!,
cmd
=>
qq(< $clean_name)
,
opts
=>
$in
,
}
);
}
else
{
open
$fh
,
'<'
,
$clean_name
or
return
$this
->_throw(
'bad open'
,
{
filename
=>
$clean_name
,
mode
=>
$mode
,
exception
=> $!,
cmd
=>
qq(< $clean_name)
,
opts
=>
$in
,
}
);
$this
->_seize(
$clean_name
,
$fh
,
$in
);
}
CORE::
binmode
(
$fh
)
if
-B
$clean_name
;
$content
= <
$fh
>;
if
(
$in
->{no_lock} ||
$this
->{opts}->{no_lock} ) {
close
$fh
or
return
$this
->_throw(
'bad close'
,
{
filename
=>
$clean_name
,
mode
=>
$mode
,
exception
=> $!,
opts
=>
$in
,
}
);
}
else
{
$this
->_release(
$fh
,
$in
);
close
$fh
or
return
$this
->_throw(
'bad close'
,
{
filename
=>
$clean_name
,
mode
=>
$mode
,
exception
=> $!,
opts
=>
$in
,
}
);
}
return
split
/
$NL
|\r|\n/o,
$content
if
$in
->{as_lines};
return
$content
;
}
sub
write_file {
my
$this
=
shift
@_
;
my
$in
=
$this
->_parse_in(
@_
);
my
$content
=
''
;
my
$raw_name
=
''
;
my
$file
=
''
;
my
$mode
=
$in
->{mode} ||
'write'
;
my
$bitmask
=
$in
->{bitmask} ||
oct
777;
my
$write_fh
;
my
(
$root
,
$path
,
$clean_name
,
@dirs
) =
(
''
,
''
,
''
, () );
$file
=
exists
$in
->{filename} &&
defined
$in
->{filename} &&
length
$in
->{filename}
?
$in
->{filename}
:
exists
$in
->{file} &&
defined
$in
->{file} &&
length
$in
->{file}
?
$in
->{file}
:
''
;
my
$maybe_file
=
shift
@_
;
$maybe_file
=
''
if
!
defined
$maybe_file
;
my
$maybe_content
=
shift
@_
;
$maybe_content
=
''
if
!
defined
$maybe_content
;
$file
=
$maybe_file
if
!
ref
$maybe_file
&&
$file
eq
''
;
$content
=
!
ref
$maybe_content
&&
!
exists
$in
->{content}
?
$maybe_content
:
$in
->{content};
my
(
$winroot
) =
$file
=~ /^(
$WINROOT
)/;
$file
=~ s/^(
$WINROOT
)//;
$file
=~ s/
$DIRSPLIT
{2,}/
$SL
/o;
$file
=~ s/
$DIRSPLIT
+$//o
unless
$file
eq SL;
$file
=
$winroot
.
$file
if
$winroot
;
$raw_name
=
$file
;
(
$root
,
$path
,
$file
) = atomize_path(
$file
);
$mode
=
'trunc'
if
$mode
eq
'truncate'
;
$content
=
''
if
$mode
eq
'trunc'
;
return
$this
->_throw(
'no input'
=> {
meth
=>
'write_file'
,
missing
=>
'a file name to create, write, or append'
,
opts
=>
$in
,
}
)
unless
length
$file
;
return
$this
->_throw(
'no input'
=> {
meth
=>
'write_file'
,
missing
=>
'the content you want to write or append'
,
opts
=>
$in
,
}
)
if
(
length
$content
== 0
&&
$mode
ne
'trunc'
&&
!
$EMPTY_WRITES_OK
&&
!
$in
->{empty_writes_OK}
&&
!
$in
->{empty_writes_ok}
);
return
$this
->_throw(
'cant write_file on a dir'
=> {
filename
=>
$raw_name
,
opts
=>
$in
,
}
)
if
-d
$raw_name
;
@dirs
=
split
/
$DIRSPLIT
/,
$path
;
foreach
(
@dirs
) {
return
$this
->_throw(
'bad chars'
=> {
string
=>
$_
,
purpose
=>
'the name of a file or directory'
,
opts
=>
$in
,
}
)
if
!
$this
->valid_filename(
$_
);
}
unshift
@dirs
,
$root
if
$root
;
unless
(
$mode
eq
'write'
||
$mode
eq
'append'
||
$mode
eq
'trunc'
) {
return
$this
->_throw(
'bad openmode popen'
=> {
meth
=>
'write_file'
,
filename
=>
$raw_name
,
badmode
=>
$mode
,
opts
=>
$in
,
}
)
}
if
( !
length
$root
&& !
length
$path
) {
$path
=
'.'
. SL;
}
else
{
$path
.= SL;
}
$clean_name
=
$root
.
$path
.
$file
;
if
( !-e
$root
.
$path
) {
my
$make_dir_ok
= 1;
my
$make_dir_return
=
$this
->make_dir(
$root
.
$path
,
exists
$in
->{dbitmask} &&
defined
$in
->{dbitmask}
?
$in
->{dbitmask}
:
oct
777,
{
diag
=>
$in
->{diag},
onfail
=>
sub
{
my
(
$err
,
$trace
) =
@_
;
return
$in
->{onfail}
if
ref
$in
->{onfail} &&
ref
$in
->{onfail} eq
'CODE'
;
$make_dir_ok
= 0;
return
$err
.
$trace
;
}
}
);
die
$make_dir_return
unless
$make_dir_ok
;
}
if
( -e
$clean_name
) {
return
$this
->_throw(
'cant fwrite'
=> {
filename
=>
$clean_name
,
dirname
=>
$root
.
$path
,
opts
=>
$in
,
}
)
unless
-w
$clean_name
;
}
else
{
return
$this
->_throw(
'cant fcreate'
=> {
filename
=>
$clean_name
,
dirname
=>
$root
.
$path
,
opts
=>
$in
,
}
)
unless
-w
$root
.
$path
;
}
if
(
$in
->{no_lock} || !
$USE_FLOCK
) {
if
( -e
$clean_name
) {
sysopen
$write_fh
,
$clean_name
,
$$MODES
{
sysopen
}{
$mode
}
or
return
$this
->_throw(
'bad open'
=> {
filename
=>
$clean_name
,
mode
=>
$mode
,
exception
=> $!,
cmd
=>
qq($clean_name, $$MODES{sysopen}{ $mode })
,
opts
=>
$in
,
}
);
}
else
{
sysopen
$write_fh
,
$clean_name
,
$$MODES
{
sysopen
}{
$mode
},
$bitmask
or
return
$this
->_throw(
'bad open'
=> {
filename
=>
$clean_name
,
mode
=>
$mode
,
exception
=> $!,
cmd
=>
qq($clean_name, $$MODES{sysopen}{$mode}, $bitmask)
,
opts
=>
$in
,
}
);
}
}
else
{
if
( -e
$clean_name
) {
open
$write_fh
,
'<'
,
$clean_name
or
return
$this
->_throw(
'bad open'
=> {
filename
=>
$clean_name
,
mode
=>
'read'
,
exception
=> $!,
cmd
=>
$mode
.
$clean_name
,
opts
=>
$in
,
}
);
my
$lockstat
=
$this
->_seize(
$clean_name
,
$write_fh
,
$in
);
return
unless
$lockstat
;
sysopen
$write_fh
,
$clean_name
,
$$MODES
{
sysopen
}{
$mode
}
or
return
$this
->_throw(
'bad open'
=> {
filename
=>
$clean_name
,
mode
=>
$mode
,
opts
=>
$in
,
exception
=> $!,
cmd
=>
qq($clean_name, $$MODES{sysopen}{ $mode })
,
}
);
}
else
{
sysopen
$write_fh
,
$clean_name
,
$$MODES
{
sysopen
}{
$mode
},
$bitmask
or
return
$this
->_throw(
'bad open'
=> {
filename
=>
$clean_name
,
mode
=>
$mode
,
opts
=>
$in
,
exception
=> $!,
cmd
=>
qq($clean_name, $$MODES{sysopen}{$mode}, $bitmask)
,
}
);
my
$lockstat
=
$this
->_seize(
$clean_name
,
$write_fh
,
$in
);
return
unless
$lockstat
;
}
if
(
$mode
ne
'append'
) {
truncate
(
$write_fh
, 0 ) or
return
$this
->_throw(
'bad systrunc'
=> {
filename
=>
$clean_name
,
exception
=> $!,
opts
=>
$in
,
}
);
}
}
CORE::
binmode
(
$write_fh
)
if
$in
->{
binmode
};
syswrite
(
$write_fh
,
$content
);
$this
->_release(
$write_fh
,
$in
)
unless
$$in
{no_lock} || !
$USE_FLOCK
;
close
$write_fh
or
return
$this
->_throw(
'bad close'
=> {
filename
=>
$clean_name
,
mode
=>
$mode
,
exception
=> $!,
opts
=>
$in
,
}
);
return
1;
}
sub
_seize {
my
(
$this
,
$file
,
$fh
,
$opts
) =
@_
;
return
$this
->_throw(
'no handle passed to _seize.'
=>
$opts
)
unless
$fh
;
$file
=
defined
$file
?
$file
:
''
;
return
$this
->_throw(
'no file name passed to _seize.'
=>
$opts
)
unless
length
$file
;
return
$fh
if
!
$CAN_FLOCK
;
my
@policy
=
@ONLOCKFAIL
;
while
(
@policy
) {
my
$fh
= &{
$_LOCKS
->{
shift
@policy
} }(
$this
,
$file
,
$fh
,
$opts
);
return
$fh
if
$fh
|| !
scalar
@policy
;
}
return
$fh
;
}
sub
_release {
my
(
$this
,
$fh
,
$opts
) =
@_
;
return
$this
->_throw(
'not a filehandle.'
=> {
opts
=>
$opts
,
argtype
=>
ref
$fh
} )
unless
$fh
&&
ref
$fh
eq
'GLOB'
;
if
(
$CAN_FLOCK
) {
flock
$fh
,
&Fcntl::LOCK_UN
}
return
1;
}
sub
valid_filename {
my
$f
= _myargs(
@_
);
$f
=~ s/
$WINROOT
//;
$f
!~ /
$ILLEGAL_CHR
/ ? 1 :
undef
;
}
sub
strip_path {
my
$arg
= _myargs(
@_
);
my
(
$stripped
) =
$arg
=~ /^.
*$DIRSPLIT
(.+)/o;
return
$stripped
if
defined
$stripped
;
return
$arg
;
}
sub
atomize_path {
my
$fqfn
= _myargs(
@_
);
$fqfn
=~ m/
$ATOMIZER
/o;
return
( $1||
''
, $2||
''
, $3||
''
);
}
sub
split_path {
my
$path
= _myargs(
@_
);
my
(
$root
,
$branch
,
$leaf
) = atomize_path(
$path
);
my
@path_dirs
=
split
/
$DIRSPLIT
/o,
$branch
;
unshift
@path_dirs
,
$root
if
$root
;
push
@path_dirs
,
$leaf
if
$leaf
;
return
@path_dirs
;
}
sub
line_count {
my
(
$this
,
$file
) =
@_
;
my
$buff
=
''
;
my
$lines
= 0;
my
$cmd
=
'<'
.
$file
;
open
my
$fh
,
'<'
,
$file
or
return
$this
->_throw(
'bad open'
,
{
'filename'
=>
$file
,
'mode'
=>
'read'
,
'exception'
=> $!,
'cmd'
=>
$cmd
,
}
);
while
(
sysread
(
$fh
,
$buff
, 4096 ) ) {
$lines
+=
$buff
=~
tr
/\n//;
$buff
=
''
;
}
close
$fh
;
return
$lines
;
}
sub
bitmask {
my
$f
= _myargs(
@_
);
defined
$f
and -e
$f
?
sprintf
(
'%04o'
,(
stat
(
$f
))[2] &
oct
777) :
undef
}
sub
can_flock {
$CAN_FLOCK
}
sub
is_readable {
my
$f
= _myargs(
@_
);
defined
$f
? -r
$f
:
undef
}
sub
is_writable {
my
$f
= _myargs(
@_
);
defined
$f
? -w
$f
:
undef
}
sub
created {
my
$f
= _myargs(
@_
);
defined
$f
and -e
$f
? $^T - ((-M
$f
) * 60 * 60 * 24) :
undef
}
sub
ebcdic {
$EBCDIC
}
sub
escape_filename {
my
(
$file
,
$escape
,
$also
) = _myargs(
@_
);
return
''
unless
defined
$file
;
$escape
=
'_'
if
!
defined
$escape
;
if
(
$also
) {
$file
=~ s/\Q
$also
\E/
$escape
/g }
$file
=~ s/
$ILLEGAL_CHR
/
$escape
/g;
$file
=~ s/
$DIRSPLIT
/
$escape
/g;
$file
}
sub
existent {
my
$f
= _myargs(
@_
);
defined
$f
? -e
$f
:
undef
}
sub
touch {
my
$this
=
shift
@_
;
my
$file
=
shift
@_
||
''
;
my
$opts
=
$this
->_remove_opts( \
@_
);
my
$path
;
return
$this
->_throw(
'no input'
,
{
meth
=>
'touch'
,
missing
=>
'a file name or file handle reference'
,
opts
=>
$opts
,
}
)
unless
defined
$file
&&
length
$file
;
$path
=
$this
->return_path(
$file
);
return
$this
->_throw(
'cant touch on a dir'
,
{
filename
=>
$file
,
dirname
=>
$path
||
''
,
opts
=>
$opts
,
}
)
if
-e
$file
&& -d
$file
;
return
$this
->_throw(
'cant dread'
,
{
filename
=>
$file
,
dirname
=>
$path
,
opts
=>
$opts
,
}
)
if
( -e
$path
&& !-r
$path
);
$this
->make_dir(
$path
)
unless
-e
$path
;
$this
->write_file(
filename
=>
$file
,
content
=>
''
,
{
empty_writes_OK
=> 1 }
)
unless
-e
$file
;
my
$now
=
time
();
return
utime
$now
,
$now
,
$file
;
}
sub
file_type {
my
$f
= _myargs(
@_
);
return
unless
defined
$f
and -e
$f
;
my
@ret
;
push
@ret
,
'PLAIN'
if
-f
$f
;
push
@ret
,
'TEXT'
if
-T
$f
;
push
@ret
,
'BINARY'
if
-B
$f
;
push
@ret
,
'DIRECTORY'
if
-d
$f
;
push
@ret
,
'SYMLINK'
if
-l
$f
;
push
@ret
,
'PIPE'
if
-p
$f
;
push
@ret
,
'SOCKET'
if
-S
$f
;
push
@ret
,
'BLOCK'
if
-b
$f
;
push
@ret
,
'CHARACTER'
if
-c
$f
;
push
@ret
,
'TTY'
if
-t
$f
;
push
@ret
,
'ERROR: Cannot determine file type'
unless
scalar
@ret
;
return
@ret
;
}
sub
flock_rules {
my
$this
=
shift
(
@_
);
my
@rules
= _myargs(
@_
);
return
@ONLOCKFAIL
unless
scalar
@rules
;
my
%valid
=
qw/
NOBLOCKEX NOBLOCKEX
NOBLOCKSH NOBLOCKSH
BLOCKEX BLOCKEX
BLOCKSH BLOCKSH
FAIL FAIL
WARN WARN
IGNORE IGNORE
UNDEF UNDEF
ZERO ZERO /
;
map
{
return
$this
->_throw(
'bad flock rules'
, {
'bad'
=>
$_
,
'all'
=> \
@rules
})
unless
exists
$valid
{
$_
}
}
@rules
;
@ONLOCKFAIL
=
@rules
;
@ONLOCKFAIL
}
sub
is_bin {
my
$f
= _myargs(
@_
);
defined
$f
? -B
$f
:
undef
}
sub
last_access {
my
$f
= _myargs(
@_
);
$f
||=
''
;
return
unless
-e
$f
;
$^T - ((-A
$f
) * 60 * 60 * 24)
}
sub
last_modified {
my
$f
= _myargs(
@_
);
$f
||=
''
;
return
unless
-e
$f
;
$^T - ((-M
$f
) * 60 * 60 * 24)
}
sub
last_changed {
my
$f
= _myargs(
@_
);
$f
||=
''
;
return
unless
-e
$f
;
$^T - ((-C
$f
) * 60 * 60 * 24)
}
sub
load_dir {
my
$this
=
shift
@_
;
my
$opts
=
$this
->_remove_opts( \
@_
);
my
$dir
=
shift
@_
;
my
@files
= ( );
my
$dir_hash
= { };
my
$dir_list
= [ ];
$dir
||=
''
;
return
$this
->_throw(
'no input'
=> {
meth
=>
'load_dir'
,
missing
=>
'a directory name'
,
opts
=>
$opts
,
}
)
unless
length
$dir
;
@files
=
$this
->list_dir(
$dir
=> {
files_only
=> 1 } );
if
( !
$opts
->{as_list} && !
$opts
->{as_listref} ) {
foreach
(
@files
) {
$dir_hash
->{
$_
} =
$this
->load_file(
$dir
. SL .
$_
);
}
return
$dir_hash
;
}
else
{
foreach
(
@files
) {
push
@$dir_list
,
$this
->load_file(
$dir
. SL .
$_
);
}
return
$dir_list
if
$opts
->{as_listref};
return
@$dir_list
;
}
return
$dir_hash
;
}
sub
make_dir {
my
$this
=
shift
@_
;
my
$opts
=
$this
->_remove_opts( \
@_
);
my
(
$dir
,
$bitmask
) =
@_
;
$bitmask
=
defined
$bitmask
?
$bitmask
:
$opts
->{bitmask};
$bitmask
||=
oct
777;
return
$this
->_throw(
'no input'
,
{
meth
=>
'make_dir'
,
missing
=>
'a directory name'
,
opts
=>
$opts
,
}
)
unless
defined
$dir
&&
length
$dir
;
if
(
$opts
->{if_not_exists} ) {
if
( -e
$dir
) {
return
$dir
if
-d
$dir
;
return
$this
->_throw(
'called mkdir on a file'
,
{
filename
=>
$dir
,
dirname
=>
join
( SL,
split
/
$DIRSPLIT
/,
$dir
) . SL,
opts
=>
$opts
,
}
);
}
}
else
{
if
( -e
$dir
) {
return
$this
->_throw(
'called mkdir on a file'
,
{
filename
=>
$dir
,
dirname
=>
join
( SL,
split
/
$DIRSPLIT
/,
$dir
) . SL,
opts
=>
$opts
,
}
)
unless
-d
$dir
;
return
$this
->_throw(
'make_dir target exists'
,
{
dirname
=>
$dir
,
filetype
=> [
$this
->file_type(
$dir
) ],
opts
=>
$opts
,
}
);
}
}
my
(
$winroot
) =
$dir
=~ /^(
$WINROOT
)/;
$dir
=~ s/^(
$WINROOT
)//;
$dir
=~ s/
$DIRSPLIT
{2,}/
$SL
/o;
$dir
=~ s/
$DIRSPLIT
+$//o
unless
$dir
eq SL;
$dir
=
$winroot
.
$dir
if
$winroot
;
my
(
$root
,
$path
) = atomize_path(
$dir
. SL );
my
@dirs_in_path
=
split
/
$DIRSPLIT
/,
$path
;
foreach
(
@dirs_in_path
) {
return
$this
->_throw(
'bad chars'
,
{
string
=>
$_
,
purpose
=>
'the name of a file or directory'
,
opts
=>
$opts
,
}
)
if
!
$this
->valid_filename(
$_
);
}
unshift
@dirs_in_path
,
$root
if
$root
;
if
(
@dirs_in_path
> 1 ) {
for
(
my
$depth
= 1;
$depth
<
@dirs_in_path
; ++
$depth
) {
if
(
$dirs_in_path
[
$depth
-1 ] eq SL ) {
$dirs_in_path
[
$depth
] = SL .
$dirs_in_path
[
$depth
]
}
else
{
$dirs_in_path
[
$depth
] =
join
SL,
@dirs_in_path
[ (
$depth
- 1 ) ..
$depth
]
}
}
}
my
$i
= 0;
foreach
(
@dirs_in_path
) {
my
$dir
=
$_
;
my
$up
= (
$i
> 0 ) ?
$dirs_in_path
[
$i
- 1 ] :
'..'
;
++
$i
;
if
( -e
$dir
&& !-d
$dir
) {
return
$this
->_throw(
'called mkdir on a file'
,
{
filename
=>
$dir
,
dirname
=>
$up
. SL,
opts
=>
$opts
,
}
);
}
next
if
-e
$dir
;
return
$this
->_throw(
'cant dcreate'
,
{
dirname
=>
$dir
,
parentd
=>
$up
,
opts
=>
$opts
,
}
)
unless
-w
$up
;
mkdir
(
$dir
,
$bitmask
) or
return
$this
->_throw(
'bad make_dir'
,
{
exception
=> $!,
dirname
=>
$dir
,
bitmask
=>
$bitmask
,
opts
=>
$opts
,
}
);
}
return
$dir
;
}
sub
abort_depth {
my
$arg
= _myargs(
@_
);
my
$this
=
shift
@_
;
if
(
defined
$arg
) {
return
File::Util->new->_throw(
'bad abort_depth'
=> {
bad
=>
$arg
} )
if
$arg
=~ /\D/;
$ABORT_DEPTH
=
$arg
;
$this
->{opts}->{abort_depth} =
$arg
if
blessed
$this
&&
$this
->{opts};
}
return
$ABORT_DEPTH
;
}
sub
onfail {
my
(
$this
,
$arg
) =
@_
;
return
unless
blessed
$this
;
$this
->{opts}->{onfail} =
$arg
if
$arg
;
return
$this
->{opts}->{onfail};
}
sub
read_limit {
my
$arg
= _myargs(
@_
);
my
$this
=
shift
@_
;
if
(
defined
$arg
) {
return
File::Util->new->_throw (
'bad read_limit'
=> {
bad
=>
$arg
} )
if
$arg
=~ /\D/;
$READ_LIMIT
=
$arg
;
$this
->{opts}->{read_limit} =
$arg
if
blessed
$this
&&
$this
->{opts};
}
return
$READ_LIMIT
;
}
sub
diagnostic {
my
$arg
= _myargs(
@_
);
my
$this
=
shift
@_
;
if
(
defined
$arg
) {
$WANT_DIAGNOSTICS
=
$arg
? 1 : 0;
$this
->{opts}->{diag} =
$arg
? 1 : 0
if
blessed
$this
&&
$this
->{opts};
}
return
$WANT_DIAGNOSTICS
;
}
sub
needs_binmode {
$NEEDS_BINMODE
}
sub
open_handle {
my
$this
=
shift
@_
;
my
$in
=
$this
->_parse_in(
@_
);
my
$file
=
''
;
my
$mode
=
''
;
my
$bitmask
=
$in
->{bitmask} ||
oct
777;
my
$raw_name
=
$file
;
my
$fh
;
my
(
$root
,
$path
,
$clean_name
,
@dirs
) =
(
''
,
''
,
''
, () );
$file
=
exists
$in
->{filename} &&
defined
$in
->{filename} &&
length
$in
->{filename}
?
$in
->{filename}
:
exists
$in
->{file} &&
defined
$in
->{file} &&
length
$in
->{file}
?
$in
->{file}
:
''
;
my
$maybe_file
=
shift
@_
;
$maybe_file
=
''
if
!
defined
$maybe_file
;
my
$maybe_mode
=
shift
@_
;
$maybe_mode
=
''
if
!
defined
$maybe_mode
;
$file
=
$maybe_file
if
!
ref
$maybe_file
&&
$file
eq
''
;
$mode
=
!
ref
$maybe_mode
&&
!
exists
$in
->{mode}
?
$maybe_mode
:
$in
->{mode};
$mode
||=
'read'
;
my
(
$winroot
) =
$file
=~ /^(
$WINROOT
)/;
$file
=~ s/^(
$WINROOT
)//;
$file
=~ s/
$DIRSPLIT
{2,}/
$SL
/o;
$file
=~ s/
$DIRSPLIT
+$//o
unless
$file
eq SL;
$file
=
$winroot
.
$file
if
$winroot
;
$raw_name
=
$file
;
(
$root
,
$path
,
$file
) = atomize_path(
$file
);
return
$this
->_throw(
'no input'
,
{
meth
=>
'open_handle'
,
missing
=>
'a file name to create, write, read/write, or append'
,
opts
=>
$in
,
}
)
unless
length
$file
;
if
(
$mode
eq
'read'
&& !-e
$raw_name
) {
return
$this
->_throw(
'no such file'
,
{
filename
=>
$raw_name
,
opts
=>
$in
,
}
)
unless
-e
$clean_name
;
}
{
my
$try_filename
=
$raw_name
;
$try_filename
=~ s/
$WINROOT
//;
return
$this
->_throw(
'bad chars'
,
{
string
=>
$raw_name
,
purpose
=>
'the name of a file or directory'
,
opts
=>
$in
,
}
)
if
$try_filename
=~ /(?:
$DIRSPLIT
){2,}/;
}
@dirs
=
split
/
$DIRSPLIT
/,
$path
;
foreach
(
@dirs
) {
return
$this
->_throw(
'bad chars'
,
{
string
=>
$_
,
purpose
=>
'the name of a file or directory'
,
opts
=>
$in
,
}
)
if
!
$this
->valid_filename(
$_
);
}
unshift
@dirs
,
$root
if
$root
;
if
(
!
exists
$in
->{use_sysopen} &&
!
defined
$in
->{use_sysopen}
) {
unless
(
exists
$$MODES
{popen}{
$mode
} &&
defined
$$MODES
{popen}{
$mode
}
) {
return
$this
->_throw(
'bad openmode popen'
,
{
meth
=>
'open_handle'
,
filename
=>
$raw_name
,
badmode
=>
$mode
,
opts
=>
$in
,
}
)
}
}
else
{
unless
(
exists
$$MODES
{
sysopen
}{
$mode
} &&
defined
$$MODES
{
sysopen
}{
$mode
}
) {
return
$this
->_throw(
'bad openmode sysopen'
,
{
meth
=>
'open_handle'
,
filename
=>
$raw_name
,
badmode
=>
$mode
,
opts
=>
$in
,
}
)
}
}
if
( !
length
$root
&& !
length
$path
) {
$path
=
'.'
. SL;
}
else
{
$path
.= SL;
}
$clean_name
=
$root
.
$path
.
$file
;
if
(
$mode
ne
'read'
&& !-e
$root
.
$path
) {
my
$make_dir_ok
= 1;
my
$make_dir_return
=
$this
->make_dir(
$root
.
$path
,
exists
$in
->{dbitmask} &&
defined
$in
->{dbitmask}
?
$in
->{dbitmask}
:
oct
777,
{
diag
=>
$in
->{diag},
onfail
=>
sub
{
my
(
$err
,
$trace
) =
@_
;
return
$in
->{onfail}
if
ref
$in
->{onfail} &&
ref
$in
->{onfail} eq
'CODE'
;
$make_dir_ok
= 0;
return
$err
.
$trace
;
}
}
);
die
$make_dir_return
unless
$make_dir_ok
;
}
if
(
$mode
eq
'write'
||
$mode
eq
'append'
||
$mode
eq
'rwcreate'
||
$mode
eq
'rwclobber'
||
$mode
eq
'rwappend'
) {
if
( -e
$clean_name
) {
return
$this
->_throw(
'cant fwrite'
,
{
filename
=>
$clean_name
,
dirname
=>
$root
.
$path
,
opts
=>
$in
,
}
)
unless
-w
$clean_name
;
}
else
{
return
$this
->_throw(
'cant fcreate'
,
{
filename
=>
$clean_name
,
dirname
=>
$root
.
$path
,
opts
=>
$in
,
}
)
unless
-w
$root
.
$path
;
}
}
elsif
(
$mode
eq
'read'
||
$mode
eq
'rwupdate'
) {
return
$this
->_throw(
'cant dread'
,
{
filename
=>
$clean_name
,
dirname
=>
$root
.
$path
,
opts
=>
$in
,
}
)
unless
-r
$root
.
$path
;
return
$this
->_throw(
'cant fread not found'
,
{
filename
=>
$clean_name
,
dirname
=>
$root
.
$path
,
opts
=>
$in
,
}
)
unless
-e
$clean_name
;
return
$this
->_throw(
'cant fread'
,
{
filename
=>
$clean_name
,
dirname
=>
$root
.
$path
,
opts
=>
$in
,
}
)
unless
-r
$clean_name
;
}
else
{
return
$this
->_throw(
'no input'
,
{
meth
=>
'open_handle'
,
missing
=>
q{a valid IO mode. (eg- 'read', 'write'...)}
,
opts
=>
$in
,
}
);
}
if
(
$$in
{no_lock} || !
$USE_FLOCK
) {
if
(
!
exists
$in
->{use_sysopen} &&
!
defined
$in
->{use_sysopen}
) {
$mode
=
$$MODES
{popen}{
$mode
};
open
$fh
,
$mode
,
$clean_name
or
return
$this
->_throw(
'bad open'
,
{
filename
=>
$clean_name
,
mode
=>
$mode
,
exception
=> $!,
cmd
=>
$mode
.
$clean_name
,
opts
=>
$in
,
}
);
}
else
{
$mode
=
$$MODES
{
sysopen
}{
$mode
};
sysopen
(
$fh
,
$clean_name
,
$$MODES
{
sysopen
}{
$mode
} ) or
return
$this
->_throw(
'bad open'
,
{
filename
=>
$clean_name
,
mode
=>
$mode
,
exception
=> $!,
cmd
=>
qq($clean_name, $$MODES{sysopen}{ $mode })
,
opts
=>
$in
,
}
);
}
}
else
{
if
(
!
exists
$in
->{use_sysopen} &&
!
defined
$in
->{use_sysopen}
) {
if
( -e
$clean_name
) {
open
$fh
,
'<'
,
$clean_name
or
return
$this
->_throw(
'bad open'
,
{
filename
=>
$clean_name
,
mode
=>
'read'
,
exception
=> $!,
cmd
=>
$mode
.
$clean_name
,
opts
=>
$in
,
}
);
my
$lockstat
=
$this
->_seize(
$clean_name
,
$fh
,
$in
);
warn
"returning $lockstat"
&&
return
$lockstat
unless
fileno
$lockstat
;
if
(
$mode
ne
'read'
) {
open
$fh
,
$$MODES
{popen}{
$mode
},
$clean_name
or
return
$this
->_throw(
'bad open'
,
{
exception
=> $!,
filename
=>
$clean_name
,
mode
=>
$mode
,
opts
=>
$in
,
cmd
=>
$$MODES
{popen}{
$mode
} .
$clean_name
,
}
);
}
}
else
{
open
$fh
,
$$MODES
{popen}{
$mode
},
$clean_name
or
return
$this
->_throw(
'bad open'
,
{
exception
=> $!,
filename
=>
$clean_name
,
mode
=>
$mode
,
opts
=>
$in
,
cmd
=>
$$MODES
{popen}{
$mode
} .
$clean_name
,
}
);
my
$lockstat
=
$this
->_seize(
$clean_name
,
$fh
,
$in
);
return
$lockstat
unless
$lockstat
;
}
}
else
{
if
( -e
$clean_name
) {
open
$fh
,
'<'
,
$clean_name
or
return
$this
->_throw(
'bad open'
,
{
filename
=>
$clean_name
,
mode
=>
'read'
,
exception
=> $!,
cmd
=>
$mode
.
$clean_name
,
opts
=>
$in
,
}
);
my
$lockstat
=
$this
->_seize(
$clean_name
,
$fh
,
$in
);
return
$lockstat
unless
$lockstat
;
sysopen
(
$fh
,
$clean_name
,
$$MODES
{
sysopen
}{
$mode
} )
or
return
$this
->_throw(
'bad open'
,
{
filename
=>
$clean_name
,
mode
=>
$mode
,
opts
=>
$in
,
exception
=> $!,
cmd
=>
qq($clean_name, $$MODES{sysopen}{ $mode })
,
}
);
}
else
{
sysopen
(
$fh
,
$clean_name
,
$$MODES
{
sysopen
}{
$mode
},
$bitmask
) or
return
$this
->_throw(
'bad open'
,
{
filename
=>
$clean_name
,
mode
=>
$mode
,
opts
=>
$in
,
exception
=> $!,
cmd
=>
qq($clean_name, $$MODES{sysopen}{$mode}, $bitmask)
,
}
);
my
$lockstat
=
$this
->_seize(
$clean_name
,
$fh
,
$in
);
return
$lockstat
unless
$lockstat
;
}
}
}
CORE::
binmode
(
$fh
)
if
$in
->{
binmode
};
return
$fh
;
}
sub
unlock_open_handle {
my
(
$this
,
$fh
) =
@_
;
return
1
unless
$USE_FLOCK
;
return
$this
->_throw(
'not a filehandle'
=> {
opts
=>
$this
->_remove_opts( \
@_
),
argtype
=>
ref
$fh
,
}
)
unless
$fh
&&
fileno
$fh
;
return
flock
(
$fh
,
&Fcntl::LOCK_UN
)
if
$CAN_FLOCK
;
return
0;
}
sub
return_path {
my
$f
= _myargs(
@_
);
$f
=~ s/(^.*)
$DIRSPLIT
.*/$1/o;
$f
}
sub
size {
my
$f
= _myargs(
@_
);
$f
||=
''
;
return
unless
-e
$f
; -s
$f
}
sub
trunc {
$_
[0]->write_file( {
mode
=>
trunc
=>
file
=>
$_
[1] } ) }
sub
use_flock {
my
$arg
= _myargs(
@_
);
$USE_FLOCK
= !!
$arg
if
defined
$arg
;
return
$USE_FLOCK
;
}
sub
AUTOLOAD {
(
my
$name
=
our
$AUTOLOAD
) =~ s/.*:://;
my
$legacy_methods
= {
can_write
=> \
&is_writable
,
can_read
=> \
&is_readable
,
isbin
=> \
&is_bin
,
readlimit
=> \
&read_limit
,
max_dives
=> \
&abort_depth
,
};
if
(
$name
eq
'_throw'
)
{
*_throw
=
sub
{
my
$this
=
shift
@_
;
my
$in
=
$this
->_parse_in(
@_
) || { };
my
$error_class
;
$in
->{diag} =
defined
$in
->{diag} && !
$in
->{diag}
? 0
:
$in
->{diag}
?
$in
->{diag}
:
$this
->{opts}->{diag};
if
(
$in
->{diag} ||
(
$in
->{opts} &&
ref
$in
->{opts} &&
ref
$in
->{opts} eq
'HASH'
&&
$in
->{opts}->{diag}
)
)
{
$error_class
=
'File::Util::Exception::Diagnostic'
;
unshift
@_
,
$this
,
$error_class
;
goto
\
&File::Util::Exception::Diagnostic::_throw
;
}
else
{
$error_class
=
'File::Util::Exception::Standard'
;
unshift
@_
,
$this
,
$error_class
;
goto
\
&File::Util::Exception::Standard::_throw
;
}
};
goto
\
&_throw
;
}
elsif
(
exists
$legacy_methods
->{
$name
} ) {
{
no
strict
'refs'
; *{
$name
} =
$legacy_methods
->{
$name
} }
goto
\
&$name
;
}
die
qq(Unknown method: File::Util::$name\n)
;
}
sub
DESTROY { }
1;