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