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   187045 use strict;
  3         24  
  3         76  
3 3     3   13 use warnings;
  3         6  
  3         74  
4              
5 3     3   1162 use parent qw( Exporter );
  3         1157  
  3         15  
6             our @EXPORT_OK = qw( dircopy fcopy rcopy );
7             our $VERSION = '0.006';
8              
9 3     3   1592 use File::Copy;
  3         11940  
  3         145  
10 3     3   18 use File::Find;
  3         5  
  3         159  
11 3     3   17 use File::Path qw( mkpath );
  3         6  
  3         177  
12 3     3   17 use File::Spec;
  3         5  
  3         3521  
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 52153 return unless @_ == 2;
170 60         168 my ($from, $to) = @_;
171             #return unless _samecheck($from, $to);
172 60 100       157 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       147 return unless _dev_ino_check($from, $to);
179              
180 56         165 return _fcopy($from, $to);
181             }
182              
183             sub _fcopy {
184 65     65   141 my ($from, $to) = @_;
185 65         783 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     860 if ( $path && !-d $path ) {
190 6         69 pathmk(File::Spec->catpath($volm, $path, ''));
191             }
192              
193 65 100 66     1273 if ( -l $from && $CopyLink ) {
    100 66        
194 4         40 my $target = readlink( $from );
195             # FCR: mass-untaint is OK since we have to allow what the file system does
196 4         22 ($target) = $target =~ m/(.*)/;
197 4 100       126 warn "Copying a symlink ($from) whose target does not exist"
198             if !-e $target;
199 4         15 my $new = $to;
200 4 50       35 unlink $new if -l $new;
201 4 50       115 symlink( $target, $new ) or return;
202             }
203 2         12 elsif (-d $from && -f $to) { return; }
204             else {
205 59 50       299 copy($from, $to) or return;
206              
207 59         15857 my @base_file = File::Spec->splitpath( $from );
208 59 100       875 my $mode_trg = -d $to ? File::Spec->catfile( $to, $base_file[$#base_file] ) : $to;
209              
210 59         1358 chmod scalar((stat($from))[2]), $mode_trg;
211             }
212 63         269 return 1;
213             }
214              
215             sub pathmk {
216 17     17 0 192 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       54 if ( defined($dir) ) {
221 17         92 my (@dirs) = File::Spec->splitdir($dir);
222              
223 17         61 for ( my $i = 0; $i < scalar(@dirs); $i++ ) {
224 86         591 my $newdir = File::Spec->catdir( @dirs[ 0 .. $i ] );
225 86         432 my $newpth = File::Spec->catpath( $vol, $newdir, "" );
226 86         1324 mkdir( $newpth );
227 86 50       1031 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       54 if ( defined($file) ) {
234 17         97 my $newpth = File::Spec->catpath( $vol, $dir, $file );
235 17         436 mkdir( $newpth );
236 17 50       204 return unless -d $newpth;
237             }
238              
239 17         59 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 42666 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       45 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         29 return _dircopy(@_);
305             }
306              
307             sub _dircopy {
308 26     26   45 my $globstar = 0;
309 26         46 my $_zero = $_[0];
310 26         46 my $_one = $_[1];
311 26 100       69 if ( substr( $_zero, ( 1 * -1 ), 1 ) eq '*' ) {
312 2         6 $globstar = 1;
313 2         6 $_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       57 return unless _dev_ino_check( $_zero, $_[1] );
326              
327 25 100 100     517 if ( !-d $_zero || ( -e $_[1] && !-d $_[1] ) ) {
      100        
328 4         13 $! = 20;
329 4         13 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       182 if ( !-d $_[1] ) {
336 11 50       36 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         51 my $baseend = $_one;
342 21         28 my $level = 0;
343 21         28 my $filen = 0;
344 21         29 my $dirn = 0;
345              
346 21         25 my $recurs; #must be my()ed before sub {} since it calls itself
347             $recurs = sub {
348 90     90   188 my ( $str, $end ) = @_;
349 90 100       183 $filen++ if $end eq $baseend;
350 90 100       145 $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     3169 mkdir( $end ) or return if !-d $end;
356 90         215 $level++;
357              
358 90 50       2234 opendir( my $str_dh, $str ) or return;
359 90   100     2063 my @entities = grep( $_ ne '.' && $_ ne '..', readdir($str_dh) );
360 90         782 closedir $str_dh;
361              
362 90         231 for my $entity (@entities) {
363 119         547 my ($entity_ut) = $entity =~ m{ (.*) }xms;
364 119         1076 my $from = File::Spec->catfile( $str, $entity_ut );
365 119         613 my $to = File::Spec->catfile( $end, $entity_ut );
366 119 100 66     2344 if ( -l $from && $CopyLink ) {
    100          
367 9         88 my $target = readlink($from);
368             # mass-untaint is OK since we have to allow what the file system does
369 9         38 ($target) = $target =~ m/(.*)/;
370 9 100       127 warn "Copying a symlink ($from) whose target does not exist"
371             if !-e $target;
372 9 50       118 unlink $to if -l $to;
373 9 50       199 symlink( $target, $to ) or return;
374             }
375             elsif ( -d $from ) {
376 69         146 my $rc;
377 69         270 $rc = $recurs->( $from, $to );
378 69 50       121 return unless $rc;
379 69         78 $filen++;
380 69         101 $dirn++;
381             }
382             else {
383 41 50       120 fcopy( $from, $to ) or return;
384 41         101 $filen++;
385             }
386             } # End 'for' loop around @entities
387 90         115 $level--;
388 90         234 1;
389              
390 21         133 }; # END definition of $recurs
391              
392 21 50       52 $recurs->( $_zero, $_one ) or return;
393 21         78 return $filen;
394             }
395              
396             sub _basic_samecheck {
397 108     108   223 my ($from, $to) = @_;
398 108 100 100     486 return if !defined $from || !defined $to;
399 100 100       245 return if $from eq $to;
400 96         262 return 1;
401             }
402              
403             sub _dev_ino_check {
404 108     108   203 my ($from, $to) = @_;
405 108 50       338 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     1655 my $one = join( '-', ( stat $from )[ 0, 1 ] ) || '';
410 108   100     1739 my $two = join( '-', ( stat $to )[ 0, 1 ] ) || '';
411 108 100 100     454 if ( $one and $one eq $two ) {
412 4         170 warn "$from and $to are identical";
413 4         40 return;
414             }
415 104         313 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 76047 return unless @_ == 2;
458 31         64 my ($from, $to) = @_;
459 31 100       70 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       49 return unless _dev_ino_check($from, $to);
466              
467             # symlinks not yet supported
468             #return if -l $_[0];
469 23 50 66     239 goto &fcopy if -l $_[0] && $CopyLink;
470              
471 21 100 100     270 goto &_dircopy if -d $_[0] || substr( $_[0], ( 1 * -1 ), 1 ) eq '*';
472 9         38 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             =head1 AUTHOR
509              
510             James E Keenan
511             CPAN ID: JKEENAN
512             jkeenan@cpan.org
513             http://thenceforward.net/perl
514              
515             =head1 COPYRIGHT
516              
517             This program is free software; you can redistribute
518             it and/or modify it under the same terms as Perl itself.
519              
520             The full text of the license can be found in the
521             LICENSE file included with this module.
522              
523             Copyright James E Keenan 2018. All rights reserved.
524              
525             =head1 SEE ALSO
526              
527             perl(1). File::Copy::Recursive(3).
528              
529             =cut
530              
531             1;
532              
533             __END__