File Coverage

blib/lib/File/Copy/Recursive/Reduced.pm
Criterion Covered Total %
statement 125 125 100.0
branch 70 86 81.4
condition 33 39 84.6
subroutine 16 16 100.0
pod 3 4 75.0
total 247 270 91.4


line stmt bran cond sub pod time code
1             package File::Copy::Recursive::Reduced;
2 3     3   484234 use strict;
  3         9  
  3         146  
3 3     3   19 use warnings;
  3         5  
  3         314  
4              
5 3     3   1618 use parent qw( Exporter );
  3         1041  
  3         20  
6             our @EXPORT_OK = qw( dircopy fcopy rcopy );
7             our $VERSION = '0.008';
8              
9 3     3   2101 use File::Copy;
  3         24488  
  3         312  
10 3     3   26 use File::Find;
  3         7  
  3         251  
11 3     3   26 use File::Path qw( mkpath );
  3         5  
  3         302  
12 3     3   22 use File::Spec;
  3         6  
  3         5924  
13              
14             our $Link = eval { local $SIG{'__DIE__'}; link '', ''; 1 } || 0;
15             our $CopyLink = eval { local $SIG{'__DIE__'}; symlink '', ''; 1 } || 0;
16             our $DirPerms = 0777;
17              
18             =encoding UTF-8
19              
20             =head1 NAME
21              
22             File::Copy::Recursive::Reduced - Recursive copying of files and directories within Perl 5 toolchain
23              
24             =head1 SYNOPSIS
25              
26             use File::Copy::Recursive::Reduced qw(fcopy dircopy);
27              
28             fcopy($orig,$new) or die $!;
29              
30             dircopy($orig,$new) or die $!;
31              
32             =head1 DESCRIPTION
33              
34             This library is intended as a not-quite-drop-in replacement for certain
35             functionality provided by L
36             File-Copy-Recursive|http://search.cpan.org/dist/File-Copy-Recursive/>. The
37             library provides methods similar enough to that distribution's C,
38             C and C functions to be usable in those CPAN distributions
39             often described as being part of the Perl toolchain.
40              
41             =head2 Rationale
42              
43             F (hereinafter referred to as B) is heavily used
44             in other CPAN libraries. Out of over 30,000 other CPAN distributions studied
45             in early 2018, it ranks by one calculation as the 129th highest distribution
46             in terms of its total direct and indirect reverse dependencies. In current
47             parlance, it sits C Hence, it ought to work
48             correctly and be installable on all operating systems where Perl is well
49             supported.
50              
51             However, as of early April 2018, FCR version 0.40 wass failing to pass its tests against either
52             Perl 5.26 or Perl 5 blead on important operating systems including Windows,
53             FreeBSD and NetBSD
54             (L). As
55             a consequence, CPAN installers such as F and F were failing to
56             install it (unless one resorted to the C<--force> option). This prevented
57             distributions dependent (directly or indirectly) on FCR from being installed
58             as well.
59              
60             Some patches had been provided to the L
61             tracker|https://rt.cpan.org/Dist/Display.html?Name=File-Copy-Recursive> for
62             this problem. However, as late as April 18 2018 those patches had not yet
63             been applied. This posed a critical problem for the ability to assess the
64             impact of the soon-to-be-released perl-5.28.0 on CPAN distributions (the
65             so-called "Blead Breaks CPAN" ("BBC") problem) on platforms other than Linux.
66              
67             F (hereinafter referred to as B) is
68             intended to provide a minimal subset of FCR's functionality -- just enough to
69             get the Perl toolchain working on the platforms where FCR is currently
70             failing. Functions will be added to FCR2 only insofar as investigation shows
71             that they can replace usage of FCR functions in toolchain and other heavily
72             used modules. No attempt will be made to reproduce all the functionality
73             currently provided or claimed to be provided by FCR.
74              
75             On April 19 2018, FCR's author, Daniel Muey, released version 0.41 to CPAN.
76             This version included a patch submitted by Tom Hukins which corrected the
77             problem addressed by FCR2. FCR once again built and tested correctly on
78             FreeBSD. That meant that its 6000-plus reverse dependencies can once again be
79             reached by F and other installers. That in turn means that we can
80             conduct exhaustive BBC investigations on FreeBSD and other platforms.
81              
82             With that correction in FCR, the original rationale for FCR2 has been
83             superseded. I will continue to maintain the code and respond to bug reports,
84             but am suspending active development. I now deem FCR2 feature-complete.
85              
86             =head1 SUBROUTINES
87              
88             The current version of FCR2 provides three exportable and publicly supported
89             subroutines partially equivalent to the similarly named subroutines exported
90             by FCR.
91              
92             =head2 C
93              
94             =over 4
95              
96             =item * Purpose
97              
98             A stripped-down replacement for C.
99              
100             Copies a file to a new location, recursively creating directories as needed.
101             Does not copy directories. Unlike C, C attempts
102             to preserve the mode of the original file.
103              
104             =item * Arguments
105              
106             fcopy($orig, $new) or die $!;
107              
108             List of two required arguments:
109              
110             =over 4
111              
112             =item * Absolute path to the file being copied; and
113              
114             =item * Absolute path to the location to which the file is being copied.
115              
116             =back
117              
118             Four cases should be noted:
119              
120             =over 4
121              
122             =item 1 Create copy within same directory but new basename
123              
124             fcopy('/path/to/filename', '/path/to/newfile');
125              
126             The second argument must be the absolute path to the new file. (Otherwise
127             the file will be created in the current working directory, which is almost
128             certainly what you do not want.)
129              
130             =item 2 Create copy within different, already B directory, same basename
131              
132             fcopy('/path/to/filename', '/path/to/existing/directory');
133              
134             The second argument can be merely the path to the existing directory; will
135             create F.
136              
137             =item 3 Create copy within different, not yet existing directory, same basename
138              
139             fcopy('/path/to/filename', '/path/not/yet/existing/directory/filename');
140              
141             The second argument will be interpreted as the complete path to the newly
142             created file. The basename must be included even if it is the same as in the
143             first argument. Will create F.
144              
145             =item 4 Create copy within different, not yet existing directory, different basename
146              
147             fcopy('/path/to/filename', '/path/not/yet/existing/directory/newfile');
148              
149             The second argument will be interpreted as the complete path to the newly
150             created file. Will create F.
151              
152             =back
153              
154             =item * Return Value
155              
156             Returns C<1> upon success; C<0> upon failure. Returns an undefined value if,
157             for example, function cannot validate arguments.
158              
159             =item * Comment
160              
161             Since C internally uses C to perform the copying,
162             the arguments are subject to the same qualifications as that function's
163             arguments. Call F for discussion of those arguments.
164              
165             =back
166              
167             =cut
168              
169             sub fcopy {
170 63 100   63 1 406696 return unless @_ == 2;
171 60         184 my ($from, $to) = @_;
172             #return unless _samecheck($from, $to);
173 60 100       195 return unless _basic_samecheck($from, $to);
174              
175             # TODO: Explore whether we should check (-e $from) here.
176             # If we don't have a starting point, it shouldn't make any sense to go
177             # farther.
178              
179 57 100       180 return unless _dev_ino_check($from, $to);
180              
181 56         175 return _fcopy($from, $to);
182             }
183              
184             sub _fcopy {
185 65     65   179 my ($from, $to) = @_;
186 65         1160 my ( $volm, $path ) = File::Spec->splitpath($to);
187              
188             # TODO: Explore whether it's possible for $path to be Perl-false in
189             # following line. If not, remove.
190 65 100 66     1328 if ( $path && !-d $path ) {
191 6         64 pathmk(File::Spec->catpath($volm, $path, ''));
192             }
193              
194 65 100 66     1471 if ( -l $from && $CopyLink ) {
    100 66        
195 4         40 my $target = readlink( $from );
196             # FCR: mass-untaint is OK since we have to allow what the file system does
197 4         22 ($target) = $target =~ m/(.*)/;
198 4 100       187 warn "Copying a symlink ($from) whose target does not exist"
199             if !-e $target;
200 4         14 my $new = $to;
201 4 50       57 unlink $new if -l $new;
202 4 50       447 symlink( $target, $new ) or return;
203             }
204 2         10 elsif (-d $from && -f $to) { return; }
205             else {
206 59 50       348 copy($from, $to) or return;
207              
208 59         36526 my @base_file = File::Spec->splitpath( $from );
209 59 100       5772 my $mode_trg = -d $to ? File::Spec->catfile( $to, $base_file[$#base_file] ) : $to;
210              
211 59         2389 chmod scalar((stat($from))[2]), $mode_trg;
212             }
213 63         398 return 1;
214             }
215              
216             sub pathmk {
217 17     17 0 311 my ( $vol, $dir, $file ) = File::Spec->splitpath( shift() );
218              
219             # TODO: Exploration whether $dir can be undef at this point.
220             # If possible, then we should probably return immediately.
221 17 50       127 if ( defined($dir) ) {
222 17         144 my (@dirs) = File::Spec->splitdir($dir);
223              
224 17         123 for ( my $i = 0; $i < scalar(@dirs); $i++ ) {
225 86         710 my $newdir = File::Spec->catdir( @dirs[ 0 .. $i ] );
226 86         517 my $newpth = File::Spec->catpath( $vol, $newdir, "" );
227 86         2730 mkdir( $newpth );
228 86 50       1210 return unless -d $newpth;
229             }
230             }
231              
232             # TODO: Exploration whether $file can be undef at this point.
233             # If possible, then we should probably return immediately.
234 17 50       80 if ( defined($file) ) {
235 17         239 my $newpth = File::Spec->catpath( $vol, $dir, $file );
236 17         1318 mkdir( $newpth );
237 17 50       286 return unless -d $newpth;
238             }
239              
240 17         74 return 1;
241             }
242              
243              
244             =head2 C
245              
246             =over 4
247              
248             =item * Purpose
249              
250             A stripped-down replacement for C.
251              
252             Given the path to the directory specified by the first argument, the function
253             copies all of the files and directories beneath it to the directory specified
254             by the second argument.
255              
256             =item * Arguments
257              
258             my $count = dircopy($orig, $new);
259             warn "dircopy() returned undefined value" unless defined $count;
260              
261             =item * Return Value
262              
263             Upon completion, returns the count of directories and files created -- which
264             might be C<0>.
265              
266             Should the function not complete (but not C), an undefined value will be
267             returned. That generally indicates problems with argument validation. This
268             approach is taken for consistency with C.
269              
270             In list context the return value is a one-item list holding the same value as
271             returned in scalar context. The three-item list return value of
272             C is not supported.
273              
274             =item * Restrictions
275              
276             None of C's bells and whistles. No guaranteed
277             preservation of file or directory modes. No restriction on maximum depth. No
278             nothing; this is fine-tuned to the needs of Perl toolchain modules and their
279             test suites.
280              
281             =back
282              
283             =cut
284              
285             sub dircopy {
286              
287             # I'm not supporting the buffer limitation, at this point I can insert a
288             # check for the correct number of arguments: 2
289             # FCR2 dircopy does not support buffer limit as third argument
290              
291 20 100   20 1 672602 return unless @_ == 2;
292              
293             # Check the definedness and string inequality of the arguments now;
294             # Failure to do it now means that if $_[0] is not defined, you'll get an
295             # uninitalized value warning in the first line that calls 'substr' below.
296              
297 17 100       70 return unless _basic_samecheck(@_);
298              
299             # See local file globstar-investigation.pl
300             # What the block above does is to trim the 'from' argument so that, if user
301             # said 'dircopy(/path/to/directory/*, /path/to/copy)', the first argument
302             # is effectively reduced to '/path/to/directory/' but inside $globstar is
303             # set to true. Have to see what impact of $globstar true is.
304              
305 14         48 return _dircopy(@_);
306             }
307              
308             sub _dircopy {
309 26     26   94 my $globstar = 0;
310 26         63 my $_zero = $_[0];
311 26         124 my $_one = $_[1];
312 26 100       154 if ( substr( $_zero, ( 1 * -1 ), 1 ) eq '*' ) {
313 2         7 $globstar = 1;
314 2         9 $_zero = substr( $_zero, 0, ( length($_zero) - 1 ) );
315             }
316              
317             # Note also that, in the above, $_[0] and $_[1], while assigned to
318             # variables, are not shifted-in. Hence they retain their original values.
319             # TODO: Investigate whether replacing $_[1] from this point forward with a
320             # 'my' variable would be harmful.
321              
322             # Both arguments must now be defined (though not necessarily true -- yet);
323             # they can't be equal; they can't be "dev-ino" equal on non-Win32 systems.
324             # Verify that.
325              
326 26 100       124 return unless _dev_ino_check( $_zero, $_[1] );
327              
328 25 100 100     660 if ( !-d $_zero || ( -e $_[1] && !-d $_[1] ) ) {
      100        
329 4         15 $! = 20;
330 4         37 return;
331             }
332              
333             # If the second argument is not an already existing directory,
334             # then, create that directory now (the top-level 'to').
335              
336 21 100       183 if ( !-d $_[1] ) {
337 11 50       51 pathmk( $_[1] ) or return;
338             }
339             # If the second argument is an existing directory ...
340             # ... $globstar false is the typical case, i.e., no '/*' at end of 2nd argument
341              
342 21         62 my $baseend = $_one;
343 21         67 my $level = 0;
344 21         43 my $filen = 0;
345 21         101 my $dirn = 0;
346              
347 21         47 my $recurs; #must be my()ed before sub {} since it calls itself
348             $recurs = sub {
349 90     90   340 my ( $str, $end ) = @_;
350 90 100       350 $filen++ if $end eq $baseend;
351 90 100       223 $dirn++ if $end eq $baseend;
352              
353             # On each pass of the recursive coderef, create the directory in the
354             # 2nd argument or return (undef) if that does not succeed
355              
356 90 100 50     10761 mkdir( $end ) or return if !-d $end;
357 90         284 $level++;
358              
359 90 50       4414 opendir( my $str_dh, $str ) or return;
360 90   100     3691 my @entities = grep( $_ ne '.' && $_ ne '..', readdir($str_dh) );
361 90         1204 closedir $str_dh;
362              
363 90         289 for my $entity (@entities) {
364 119         786 my ($entity_ut) = $entity =~ m{ (.*) }xms;
365 119         1930 my $from = File::Spec->catfile( $str, $entity_ut );
366 119         1093 my $to = File::Spec->catfile( $end, $entity_ut );
367 119 100 66     3603 if ( -l $from && $CopyLink ) {
    100          
368 9         429 my $target = readlink($from);
369             # mass-untaint is OK since we have to allow what the file system does
370 9         64 ($target) = $target =~ m/(.*)/;
371 9 100       254 warn "Copying a symlink ($from) whose target does not exist"
372             if !-e $target;
373 9 50       325 unlink $to if -l $to;
374 9 50       1028 symlink( $target, $to ) or return;
375             }
376             elsif ( -d $from ) {
377 69         154 my $rc;
378 69         507 $rc = $recurs->( $from, $to );
379 69 50       183 return unless $rc;
380 69         111 $filen++;
381 69         187 $dirn++;
382             }
383             else {
384 41 50       151 fcopy( $from, $to ) or return;
385 41         135 $filen++;
386             }
387             } # End 'for' loop around @entities
388 90         170 $level--;
389 90         408 1;
390              
391 21         218 }; # END definition of $recurs
392              
393 21 50       74 $recurs->( $_zero, $_one ) or return;
394 21         140 return $filen;
395             }
396              
397             sub _basic_samecheck {
398 108     108   265 my ($from, $to) = @_;
399 108 100 100     868 return if !defined $from || !defined $to;
400 100 100       366 return if $from eq $to;
401 96         329 return 1;
402             }
403              
404             sub _dev_ino_check {
405 108     108   306 my ($from, $to) = @_;
406 108 50       512 return 1 if $^O eq 'MSWin32';
407              
408             # perldoc perlport: "(Win32) "dev" and "ino" are not meaningful."
409             # Will probably have to add restrictions for VMS and other OSes.
410 108   100     2635 my $one = join( '-', ( stat $from )[ 0, 1 ] ) || '';
411 108   100     3703 my $two = join( '-', ( stat $to )[ 0, 1 ] ) || '';
412 108 100 100     635 if ( $one and $one eq $two ) {
413 4         222 warn "$from and $to are identical";
414 4         71 return;
415             }
416 104         461 return 1;
417             }
418              
419             =head2 C
420              
421             =over 4
422              
423             =item * Purpose
424              
425             A stripped-down replacement for C. As is the
426             case with that FCR function, C is more or less a wrapper around
427             C or C, depending on the nature of the first argument.
428              
429             =item * Arguments
430              
431             rcopy($orig, $new) or die $!;
432              
433             List of two required arguments:
434              
435             =over 4
436              
437             =item * Absolute path to the entity (file or directory) being copied; and
438              
439             =item * Absolute path to the location to which the entity is being copied.
440              
441             =back
442              
443             =item * Return Value
444              
445             Returns C<1> upon success; C<0> upon failure. Returns an undefined value if,
446             for example, function cannot validate arguments.
447              
448             =item * Comment
449              
450             Please read the documentation for C or C, depending on the
451             nature of the first argument.
452              
453             =back
454              
455             =cut
456              
457             sub rcopy {
458 37 100   37 1 487169 return unless @_ == 2;
459 31         108 my ($from, $to) = @_;
460 31 100       103 return unless _basic_samecheck($from, $to);
461              
462             # TODO: Explore whether we should check (-e $from) here.
463             # If we don't have a starting point, it shouldn't make any sense to go
464             # farther.
465              
466 25 100       68 return unless _dev_ino_check($from, $to);
467              
468             # symlinks not yet supported
469             #return if -l $_[0];
470 23 50 66     337 goto &fcopy if -l $_[0] && $CopyLink;
471              
472 21 100 100     316 goto &_dircopy if -d $_[0] || substr( $_[0], ( 1 * -1 ), 1 ) eq '*';
473 9         53 goto &_fcopy;
474             }
475              
476              
477             =head2 File::Copy::Recursive Subroutines Not Supported in File::Copy::Recursive::Reduced
478              
479             As of the current version, FCR2 has no publicly documented, exportable subroutines equivalent
480             to the following FCR exportable subroutines:
481              
482             rcopy_glob
483             fmove
484             rmove
485             rmove_glob
486             dirmove
487             pathempty
488             pathrm
489             pathrmdir
490              
491             Consideration is being given to supporting C.
492              
493             =head1 BUGS AND SUPPORT
494              
495             Please report any bugs by mail to C
496             or through the web interface at L.
497              
498             =head1 ACKNOWLEDGEMENTS
499              
500             Notwithstanding the fact that this distribution is being released to address
501             certain problems in File-Copy-Recursive, credit must be given to FCR author
502             L for ingenious
503             conception and execution. The implementation of the subroutines provided by
504             FCR2 follows that found in FCR to a significant extent.
505              
506             Thanks also to Tom Hukins for supplying the patch which corrects FCR's
507             problems and which has been incorporated into FCR2 as well.
508              
509             Thanks to Håkon Hægland for paying attention to how this library performs on
510             Windows and other platforms to which the author does not have access.
511              
512             =head1 AUTHOR
513              
514             James E Keenan
515             CPAN ID: JKEENAN
516             jkeenan@cpan.org
517             http://thenceforward.net/perl
518              
519             =head1 COPYRIGHT
520              
521             This program is free software; you can redistribute
522             it and/or modify it under the same terms as Perl itself.
523              
524             The full text of the license can be found in the
525             LICENSE file included with this module.
526              
527             Copyright James E Keenan 2018-2023. All rights reserved.
528              
529             =head1 SEE ALSO
530              
531             perl(1). File::Copy::Recursive(3).
532              
533             =cut
534              
535             1;
536              
537             __END__