File Coverage

blib/lib/Overload/FileCheck.pm
Criterion Covered Total %
statement 214 225 95.1
branch 126 148 85.1
condition 30 45 66.6
subroutine 31 32 96.8
pod 12 13 92.3
total 413 463 89.2


line stmt bran cond sub pod time code
1             # Copyright (c) 2018, cPanel, LLC.
2             # All rights reserved.
3             # http://cpanel.net
4             #
5             # This is free software; you can redistribute it and/or modify it under the
6             # same terms as Perl itself. See L.
7              
8             package Overload::FileCheck;
9              
10 49     49   10055676 use strict;
  49         115  
  49         1718  
11 49     49   272 use warnings;
  49         104  
  49         2305  
12              
13             # ABSTRACT: override/mock perl file check -X: -e, -f, -d, ...
14              
15 49     49   20681 use Errno ();
  49         76768  
  49         1424  
16              
17 49     49   300 use base 'Exporter';
  49         182  
  49         7238  
18              
19             BEGIN {
20              
21 49     49   180 our $VERSION = '0.014'; # VERSION: generated by DZP::OurPkgVersion
22              
23 49         217 require XSLoader;
24 49         44454 XSLoader::load(__PACKAGE__);
25             }
26              
27             use Fcntl (
28 49         61527 '_S_IFMT', # bit mask for the file type bit field
29             #'S_IFPERMS', # bit mask for file perms.
30             'S_IFSOCK', # socket
31             'S_IFLNK', # symbolic link
32             'S_IFREG', # regular file
33             'S_IFBLK', # block device
34             'S_IFDIR', # directory
35             'S_IFCHR', # character device
36             'S_IFIFO', # FIFO
37              
38             # qw{S_IRUSR S_IWUSR S_IXUSR S_IRWXU}
39 49     49   388 );
  49         77  
40              
41             my @STAT_T_IX = qw{
42             ST_DEV
43             ST_INO
44             ST_MODE
45             ST_NLINK
46             ST_UID
47             ST_GID
48             ST_RDEV
49             ST_SIZE
50             ST_ATIME
51             ST_MTIME
52             ST_CTIME
53             ST_BLKSIZE
54             ST_BLOCKS
55             };
56              
57             my @CHECK_STATUS = qw{CHECK_IS_FALSE CHECK_IS_TRUE FALLBACK_TO_REAL_OP};
58              
59             my @STAT_HELPERS = qw{ stat_as_directory stat_as_file stat_as_symlink
60             stat_as_socket stat_as_chr stat_as_block};
61              
62             our @EXPORT_OK = (
63             qw{
64             mock_all_from_stat
65             mock_all_file_checks mock_file_check mock_stat
66             unmock_file_check unmock_all_file_checks unmock_stat
67             },
68             @CHECK_STATUS,
69             @STAT_T_IX,
70             @STAT_HELPERS,
71             );
72              
73             our %EXPORT_TAGS = (
74             all => [@EXPORT_OK],
75              
76             # status code
77             check => [@CHECK_STATUS],
78              
79             # STAT array indexes
80             stat => [ @STAT_T_IX, @STAT_HELPERS ],
81             );
82              
83             # hash for every filecheck we can mock
84             # and their corresonding OP_TYPE
85             my %MAP_FC_OP = (
86             'R' => OP_FTRREAD,
87             'W' => OP_FTRWRITE,
88             'X' => OP_FTREXEC,
89             'r' => OP_FTEREAD,
90             'w' => OP_FTEWRITE,
91             'x' => OP_FTEEXEC,
92              
93             'e' => OP_FTIS,
94             's' => OP_FTSIZE, # OP_CAN_RETURN_INT
95             'M' => OP_FTMTIME, # OP_CAN_RETURN_INT
96             'C' => OP_FTCTIME, # OP_CAN_RETURN_INT
97             'A' => OP_FTATIME, # OP_CAN_RETURN_INT
98              
99             'O' => OP_FTROWNED,
100             'o' => OP_FTEOWNED,
101             'z' => OP_FTZERO,
102             'S' => OP_FTSOCK,
103             'c' => OP_FTCHR,
104             'b' => OP_FTBLK,
105             'f' => OP_FTFILE,
106             'd' => OP_FTDIR,
107             'p' => OP_FTPIPE,
108             'u' => OP_FTSUID,
109             'g' => OP_FTSGID,
110             'k' => OP_FTSVTX,
111              
112             'l' => OP_FTLINK,
113              
114             't' => OP_FTTTY,
115              
116             'T' => OP_FTTEXT,
117             'B' => OP_FTBINARY,
118              
119             # special cases for stat & lstat
120             'stat' => OP_STAT,
121             'lstat' => OP_LSTAT,
122              
123             );
124              
125             my %MAP_STAT_T_IX = (
126             st_dev => ST_DEV,
127             st_ino => ST_INO,
128             st_mode => ST_MODE,
129             st_nlink => ST_NLINK,
130             st_uid => ST_UID,
131             st_gid => ST_GID,
132             st_rdev => ST_RDEV,
133             st_size => ST_SIZE,
134             st_atime => ST_ATIME,
135             st_mtime => ST_MTIME,
136             st_ctime => ST_CTIME,
137             st_blksize => ST_BLKSIZE,
138             st_blocks => ST_BLOCKS,
139             );
140              
141             # op_type_id => check
142             my %REVERSE_MAP;
143              
144             my %OP_CAN_RETURN_INT = map { $MAP_FC_OP{$_} => 1 } qw{ s M C A };
145             my %OP_IS_STAT_OR_LSTAT = map { $MAP_FC_OP{$_} => 1 } qw{ stat lstat };
146             #
147             # This is listing the default ERRNO codes
148             # used by each test when the test fails and
149             # the user did not provide one ERRNO error
150             #
151             my %DEFAULT_ERRNO = (
152             'default' => Errno::ENOENT, # default value for any other not listed
153             'x' => Errno::ENOEXEC,
154             'X' => Errno::ENOEXEC,
155              
156             # ...
157             );
158              
159             # this is saving our custom ops
160             # optype_id => sub
161             my $_current_mocks = {};
162              
163             sub import {
164 48     48   570 my ( $class, @args ) = @_;
165              
166             # mock on import...
167 48         188 my $_next_check;
168             my @for_exporter;
169 48         197 foreach my $check (@args) {
170 63 100 100     1128 if ( !$_next_check && $check !~ qr{^-} ) {
171              
172             # this is a valid arg for exporter
173 57         214 push @for_exporter, $check;
174 57         211 next;
175             }
176 6 100       20 if ( !$_next_check ) {
177              
178             # we found a key like '-e' in '-e => sub {} '
179 3         16 $_next_check = $check;
180             }
181             else {
182             # now this is the value
183 3         7 my $code = $check;
184              
185             # use Overload::FileCheck -from_stat => \&my_stat;
186 3 100 66     14 if ( $_next_check eq q{-from_stat} || $_next_check eq q{-from-stat} ) {
187 2         5 mock_all_from_stat($code);
188             }
189             else {
190 1         3 mock_file_check( $_next_check, $code );
191             }
192              
193 3         8 undef $_next_check;
194             }
195             }
196              
197             # callback the exporter logic
198 48         83355 return __PACKAGE__->export_to_level( 1, $class, @for_exporter );
199             }
200              
201             sub mock_all_file_checks {
202 10     10 0 384627 my ($sub) = @_;
203              
204 10         198 foreach my $check ( sort keys %MAP_FC_OP ) {
205 290 100       1199 next if $check =~ qr{^l?stat$}; # we should not mock stat
206             mock_file_check(
207             $check,
208             sub {
209 532     532   1984 my (@args) = @_;
210 532         1402 return $sub->( $check, @args );
211             }
212 270         1128 );
213             }
214              
215 10         42 return 1;
216             }
217              
218             sub mock_file_check {
219 300     300 1 9298285 my ( $check, $sub ) = @_;
220              
221 300 50       703 die q[Check is not defined] unless defined $check;
222 300 50       771 die q[Second arg must be a CODE ref] unless ref $sub eq 'CODE';
223              
224 300         550 $check =~ s{^-+}{}; # strip any extra dashes
225             #return -1 unless defined $MAP_FC_OP{$check}; # we should not do that
226 300 50       669 die qq[Unknown check '$check'] unless defined $MAP_FC_OP{$check};
227              
228 300         456 my $optype = $MAP_FC_OP{$check};
229 300 100       640 die qq[-$check is already mocked by Overload::FileCheck] if exists $_current_mocks->{$optype};
230              
231 299         636 $_current_mocks->{$optype} = $sub;
232              
233 299         740 _xs_mock_op($optype);
234              
235 299         635 return 1;
236             }
237              
238             sub unmock_file_check {
239 31     31 1 6280 my (@checks) = @_;
240              
241 31         101 foreach my $check (@checks) {
242 144 50       337 die q[Check is not defined] unless defined $check;
243 144         270 $check =~ s{^-+}{}; # strip any extra dashes
244 144 50       365 die qq[Unknown check '$check'] unless defined $MAP_FC_OP{$check};
245              
246 144         266 my $optype = $MAP_FC_OP{$check};
247              
248 144         545 delete $_current_mocks->{$optype};
249              
250 144         413 _xs_unmock_op($optype);
251             }
252              
253 31         278 return 1;
254             }
255              
256             sub mock_all_from_stat {
257 8     8 1 1560341 my ($sub_for_stat) = @_;
258              
259             # then mock all -X checks to our custom
260             mock_all_file_checks(
261             sub {
262 366     366   842 my ( $check, $f_or_fh ) = @_;
263              
264             # the main call
265 366         1017 my $return = _check_from_stat( $check, $f_or_fh, $sub_for_stat );
266              
267             # auto remock the OP (it could have been temporary unmocked to use -X _)
268 366         1282 _xs_mock_op( $MAP_FC_OP{$check} );
269              
270 366         1115 return $return;
271             }
272 8         84 );
273              
274             # start by mocking 'stat' and 'lstat' call
275 8         39 mock_stat($sub_for_stat);
276              
277 8         64 return 1;
278             }
279              
280             sub _check_from_stat {
281 366     366   767 my ( $check, $f_or_fh, $sub_for_stat ) = @_;
282              
283 366         737 my $optype = $MAP_FC_OP{$check};
284              
285             # stat would need to be called twice
286             # 1/ we first need to check if we are mocking the file
287             # or if we let it fallback to the Perl OP
288             # 2/ doing a second stat call in order to cache _
289              
290 366         542 my $can_use_stat;
291 366 100       2744 $can_use_stat = 1 if $check =~ qr{^[sfdMXxzACORWeorw]$};
292              
293 366 100       1249 my $stat_or_lstat = $can_use_stat ? 'stat' : 'lstat';
294              
295 366         1241 my (@mocked_lstat_result) = $sub_for_stat->( $stat_or_lstat, $f_or_fh );
296 366 100 66     9439 if ( scalar @mocked_lstat_result == 1
      66        
297             && !ref $mocked_lstat_result[0]
298             && $mocked_lstat_result[0] == FALLBACK_TO_REAL_OP ) {
299 6         25 return FALLBACK_TO_REAL_OP;
300             }
301              
302             # avoid a second callback to the user hook (do not really happen for now)
303             local $_current_mocks->{ $MAP_FC_OP{$stat_or_lstat} } = sub {
304 360     360   804 return @mocked_lstat_result;
305 360         2431 };
306              
307             # now performing a real stat call [ using the mocked stat function ]
308 360         799 my ( @stat, @lstat );
309              
310 360 100       748 if ($can_use_stat) {
311 49     49   385 no warnings; # throw warnings with Perl <= 5.14
  49         142  
  49         3503  
312 317 50       1549 @stat = stat($f_or_fh) if defined $f_or_fh;
313             }
314             else {
315 49     49   323 no warnings;
  49         115  
  49         103069  
316 43 50       243 @lstat = lstat($f_or_fh) if defined $f_or_fh;
317             }
318              
319 360 100       2709 if ( $check eq 'r' ) {
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    50          
    50          
    50          
    100          
    100          
    100          
    100          
    100          
    50          
320              
321             # -r File is readable by effective uid/gid.
322             # return _cando(stat_mode, effective, &PL_statcache)
323             # return _cando( S_IRUSR, 1 )
324              
325             # ugly need a better way to do this...
326 19         57 _xs_unmock_op($optype);
327 19         123 return _to_bool( scalar -r _ );
328             }
329             elsif ( $check eq 'w' ) {
330              
331             # -w File is writable by effective uid/gid.
332 19         60 _xs_unmock_op($optype);
333 19         126 return _to_bool( scalar -w _ );
334             }
335             elsif ( $check eq 'x' ) {
336              
337             # -x File is executable by effective uid/gid.
338 13         44 _xs_unmock_op($optype);
339 13         112 return _to_bool( scalar -x _ );
340             }
341             elsif ( $check eq 'o' ) {
342              
343             # -o File is owned by effective uid.
344 19         59 _xs_unmock_op($optype);
345 19         145 return _to_bool( scalar -o _ );
346             }
347             elsif ( $check eq 'R' ) {
348              
349             # -R File is readable by real uid/gid.
350 19         60 _xs_unmock_op($optype);
351 19         121 return _to_bool( scalar -R _ );
352             }
353             elsif ( $check eq 'W' ) {
354              
355             # -W File is writable by real uid/gid.
356 19         56 _xs_unmock_op($optype);
357 19         152 return _to_bool( scalar -W _ );
358             }
359             elsif ( $check eq 'X' ) {
360              
361             # -X File is executable by real uid/gid.
362              
363 12         44 _xs_unmock_op($optype);
364 12         82 return _to_bool( scalar -X _ );
365             }
366             elsif ( $check eq 'O' ) {
367              
368             # -O File is owned by real uid.
369 19         59 _xs_unmock_op($optype);
370 19         119 return _to_bool( scalar -O _ );
371             }
372             elsif ( $check eq 'e' ) {
373              
374             # -e File exists.
375             # a file can only exists if MODE is set ?
376 39   100     164 return _to_bool( scalar @stat && $stat[ST_MODE] );
377             }
378             elsif ( $check eq 'z' ) {
379              
380             # -z File has zero size (is empty).
381              
382             # TODO: can probably avoid the extra called...
383             # by checking it ourself
384              
385 9         47 _xs_unmock_op($optype);
386 9         28 return _to_bool( scalar -z _ );
387             }
388             elsif ( $check eq 's' ) {
389              
390             # -s File has nonzero size (returns size in bytes).
391              
392             # fallback does not work with symlinks
393             # do the check ourself, which also save a few calls
394              
395 16         108 return $stat[ST_SIZE];
396             }
397             elsif ( $check eq 'f' ) {
398              
399             # -f File is a plain file.
400 21         72 return _check_mode_type( $stat[ST_MODE], S_IFREG );
401             }
402             elsif ( $check eq 'd' ) {
403              
404             # -d File is a directory.
405              
406 36         101 return _check_mode_type( $stat[ST_MODE], S_IFDIR );
407             }
408             elsif ( $check eq 'l' ) {
409              
410             # -l File is a symbolic link (false if symlinks aren't
411             # supported by the file system).
412              
413 13         72 return _check_mode_type( $lstat[ST_MODE], S_IFLNK );
414             }
415             elsif ( $check eq 'p' ) {
416              
417             # -p File is a named pipe (FIFO), or Filehandle is a pipe.
418 1         4 return _check_mode_type( $lstat[ST_MODE], S_IFIFO );
419             }
420             elsif ( $check eq 'S' ) {
421              
422             # -S File is a socket.
423 5         16 return _check_mode_type( $lstat[ST_MODE], S_IFSOCK );
424             }
425             elsif ( $check eq 'b' ) {
426              
427             # -b File is a block special file.
428 2         9 return _check_mode_type( $lstat[ST_MODE], S_IFBLK );
429             }
430             elsif ( $check eq 'c' ) {
431              
432             # -c File is a character special file.
433 2         7 return _check_mode_type( $lstat[ST_MODE], S_IFCHR );
434             }
435             elsif ( $check eq 't' ) {
436              
437             # -t Filehandle is opened to a tty.
438 0         0 _xs_unmock_op($optype);
439 0         0 return _to_bool( scalar -t _ );
440             }
441             elsif ( $check eq 'u' ) {
442              
443             # -u File has setuid bit set.
444 0         0 _xs_unmock_op($optype);
445 0         0 return _to_bool( scalar -u _ );
446             }
447             elsif ( $check eq 'g' ) {
448              
449             # -g File has setgid bit set.
450 0         0 _xs_unmock_op($optype);
451 0         0 return _to_bool( scalar -g _ );
452             }
453             elsif ( $check eq 'k' ) {
454              
455             # -k File has sticky bit set.
456              
457 1         5 _xs_unmock_op($optype);
458 1         4 return _to_bool( scalar -k _ );
459             }
460             elsif ( $check eq 'T' ) { # heuristic guess.. throw a die?
461              
462             # -T File is an ASCII or UTF-8 text file (heuristic guess).
463              
464             #return CHECK_IS_FALSE if -d $f_or_fh;
465              
466 5         16 _xs_unmock_op($optype);
467 5         138 return _to_bool( scalar -T *_ );
468             }
469             elsif ( $check eq 'B' ) { # heuristic guess.. throw a die?
470              
471             # -B File is a "binary" file (opposite of -T).
472              
473 14 100       54 return CHECK_IS_TRUE if -d $f_or_fh;
474              
475             # ... we cannot really know...
476             # ... this is an heuristic guess...
477              
478 4         15 _xs_unmock_op($optype);
479 4         138 return _to_bool( scalar -B *_ );
480             }
481             elsif ( $check eq 'M' ) {
482              
483             # -M Script start time minus file modification time, in days.
484              
485 19 50 33     92 return CHECK_IS_NULL unless scalar @stat && defined $stat[ST_MTIME];
486 19         161 return ( ( get_basetime() - $stat[ST_MTIME] ) / 86400.0 );
487              
488             #return int( scalar -M _ );
489             }
490             elsif ( $check eq 'A' ) {
491              
492             # -A Same for access time.
493             #
494             # ((NV)PL_basetime - PL_statcache.st_atime) / 86400.0
495 19 50 33     85 return CHECK_IS_NULL unless scalar @stat && defined $stat[ST_ATIME];
496              
497 19         208 return ( ( get_basetime() - $stat[ST_ATIME] ) / 86400.0 );
498             }
499             elsif ( $check eq 'C' ) {
500              
501             # -C Same for inode change time (Unix, may differ for other
502             #_xs_unmock_op($optype);
503             #return scalar -C *_;
504 19 50 33     105 return CHECK_IS_NULL unless scalar @stat && defined $stat[ST_CTIME];
505              
506 19         168 return ( ( get_basetime() - $stat[ST_CTIME] ) / 86400.0 );
507             }
508             else {
509 0         0 die "Unknown check $check.\n";
510             }
511              
512 0         0 die "FileCheck -$check is not implemented by Overload::FileCheck...";
513              
514 0         0 return FALLBACK_TO_REAL_OP;
515             }
516              
517             sub _to_bool {
518 274     274   566 my ($s) = @_;
519              
520 274 100       2066 return ( $s ? CHECK_IS_TRUE : CHECK_IS_FALSE );
521             }
522              
523             sub _check_mode_type {
524 80     80   184 my ( $mode, $type ) = @_;
525              
526 80 100       202 return CHECK_IS_FALSE unless defined $mode;
527 77         213 return _to_bool( ( $mode & _S_IFMT ) == $type );
528             }
529              
530             # this is a special case used to mock OP_STAT & OP_LSTAT
531             sub mock_stat {
532 10     10 1 312057 my ($sub) = @_;
533              
534 10 50       60 die q[First arg must be a CODE ref] unless ref $sub eq 'CODE';
535              
536 10         38 foreach my $opname (qw{stat lstat}) {
537 20         68 my $optype = $MAP_FC_OP{$opname};
538 20 50       51 die qq[No optype found for $opname] unless $optype;
539              
540             # plug the sub
541             $_current_mocks->{$optype} = sub {
542 34     34   53 my $file_or_handle = shift;
543 34         351 return $sub->( $opname, $file_or_handle );
544 20         448 };
545              
546             # setup the mock for the OP
547 20         66 _xs_mock_op($optype);
548             }
549              
550 10         25 return 1;
551             }
552              
553             # just an alias to unmock stat & lstat at the same time
554             sub unmock_stat {
555 0     0 1 0 return unmock_file_check(qw{stat lstat});
556             }
557              
558             sub unmock_all_file_checks {
559              
560 10 100   10 1 6262 if ( !scalar %REVERSE_MAP ) {
561 8         87 foreach my $k ( keys %MAP_FC_OP ) {
562 232         675 $REVERSE_MAP{ $MAP_FC_OP{$k} } = $k;
563             }
564             }
565              
566 10         67 my @mocks = sort map { $REVERSE_MAP{$_} } keys %$_current_mocks;
  122         341  
567 10 50       75 return unless scalar @mocks;
568              
569 10         49 return unmock_file_check(@mocks);
570             }
571              
572             # should not be called directly
573             # this is called from XS to check if one OP is mocked
574             # and trigger the callback function when mocked
575             my $_last_call_for;
576              
577             sub _check {
578 1282     1282   1844123 my ( $optype, $file, @others ) = @_;
579              
580 1282 50       3531 die if scalar @others; # need to move this in a unit test
581              
582             # we have no custom mock at this point
583 1282 50       3989 return FALLBACK_TO_REAL_OP unless defined $_current_mocks->{$optype};
584              
585 1282 50 66     3278 $file = $_last_call_for if !defined $file && defined $_last_call_for && !defined $_current_mocks->{ $MAP_FC_OP{'stat'} };
      66        
586 1282         3303 my ( $out, @extra ) = $_current_mocks->{$optype}->($file);
587             # Only cache string filenames, not filehandle references.
588             # Storing a ref here prevents the filehandle from being garbage collected,
589             # causing resource leaks (e.g. sockets staying open). See GH #179.
590 1282 100       277910 $_last_call_for = ref($file) ? undef : $file;
591              
592             # FIXME return undef when not defined out
593              
594 1282 100 66     5961 if ( defined $out && $OP_CAN_RETURN_INT{$optype} ) {
595 139         2195 return $out; # limitation to int for now in fact some returns NVs
596             }
597              
598 1143 100       2662 if ( !$out ) {
599              
600             # check if the user provided a custom ERRNO error otherwise
601             # set one for him, so a test could never fail without having
602             # ERRNO set
603 200 100       1272 if ( !int($!) ) {
604 21   66     216 $! = $DEFAULT_ERRNO{ $REVERSE_MAP{$optype} || 'default' } || $DEFAULT_ERRNO{'default'};
605             }
606              
607             #return undef unless defined $out;
608 200         2817 return CHECK_IS_FALSE;
609             }
610              
611 943 100 100     9159 return FALLBACK_TO_REAL_OP if !ref $out && $out == FALLBACK_TO_REAL_OP;
612              
613             # stat and lstat OP are returning a stat ARRAY in addition to the status code
614 801 100       1836 if ( $OP_IS_STAT_OR_LSTAT{$optype} ) {
615              
616             # .......... Stat_t
617             # dev_t st_dev Device ID of device containing file.
618             # ino_t st_ino File serial number.
619             # mode_t st_mode Mode of file (see below).
620             # nlink_t st_nlink Number of hard links to the file.
621             # uid_t st_uid User ID of file.
622             # gid_t st_gid Group ID of file.
623             # dev_t st_rdev Device ID (if file is character or block special).
624             # off_t st_size For regular files, the file size in bytes.
625             # time_t st_atime Time of last access.
626             # time_t st_mtime Time of last data modification.
627             # time_t st_ctime Time of last status change.
628             # blksize_t st_blksize A file system-specific preferred I/O block size for
629             # blkcnt_t st_blocks Number of blocks allocated for this object.
630             # ......
631              
632 391   33     994 my $stat = $out // $others[0]; # can be array or hash at this point
633 391         878 my $stat_is_a = ref $stat;
634 391 100       848 die q[Your mocked function for stat should return a stat array or hash] unless $stat_is_a;
635              
636 389         532 my $stat_as_arrayref;
637              
638             # can handle one ARRAY or a HASH
639 389         613 my $stat_t_max = STAT_T_MAX;
640 389 100       792 if ( $stat_is_a eq 'ARRAY' ) {
    50          
641 383         584 $stat_as_arrayref = $stat;
642 383         604 my $av_size = scalar @$stat;
643 383 100 100     1428 if (
644             $av_size # 0 is valid when the file is missing
645             && $av_size != $stat_t_max
646             ) {
647 4         81 die qq[Stat array should contain exactly 0 or $stat_t_max values];
648             }
649             }
650             elsif ( $stat_is_a eq 'HASH' ) {
651 6         21 $stat_as_arrayref = [ (0) x $stat_t_max ]; # start with an empty array
652 6         21 foreach my $k ( keys %$stat ) {
653 8         22 my $ix = $MAP_STAT_T_IX{ lc($k) };
654 8 100       66 die qq[Unknown index for stat_t struct key $k] unless defined $ix;
655 6         15 $stat_as_arrayref->[$ix] = $stat->{$k};
656             }
657             }
658             else {
659 0         0 die q[Your mocked function for stat should return a stat array or hash];
660             }
661              
662 383         2739 return ( CHECK_IS_TRUE, $stat_as_arrayref );
663             }
664              
665 410         4662 return CHECK_IS_TRUE;
666             }
667              
668             # accessors for testing purpose mainly
669             sub _get_filecheck_ops_map {
670 42     42   384343 return {%MAP_FC_OP}; # return a copy
671             }
672              
673             ######################################################
674             ### stat helpers
675             ######################################################
676              
677             sub stat_as_directory {
678 2     2 1 368603 my (%opts) = @_;
679              
680 2         11 return _stat_for( S_IFDIR, \%opts );
681             }
682              
683             sub stat_as_file {
684 18     18 1 11107 my (%opts) = @_;
685              
686 18         63 return _stat_for( S_IFREG, \%opts );
687             }
688              
689             sub stat_as_symlink {
690 2     2 1 6 my (%opts) = @_;
691              
692 2         7 return _stat_for( S_IFLNK, \%opts );
693             }
694              
695             sub stat_as_socket {
696 2     2 1 4 my (%opts) = @_;
697              
698 2         7 return _stat_for( S_IFSOCK, \%opts );
699             }
700              
701             sub stat_as_chr {
702 1     1 1 3 my (%opts) = @_;
703              
704 1         2 return _stat_for( S_IFCHR, \%opts );
705             }
706              
707             sub stat_as_block {
708 1     1 1 2 my (%opts) = @_;
709              
710 1         3 return _stat_for( S_IFBLK, \%opts );
711             }
712              
713             sub _stat_for {
714 26     26   48 my ( $type, $opts ) = @_;
715              
716 26         85 my @stat = ( (0) x 13 ); # STAT_T_MAX
717              
718             # set file type
719 26 50       70 if ( defined $type ) {
720              
721             # _S_IFMT is used as a protection to do not flip outside the mask
722 26         70 $stat[ST_MODE] |= ( $type & _S_IFMT );
723             }
724              
725             # set permission using octal
726 26 100       65 if ( defined $opts->{perms} ) {
727              
728             # _S_IFMT is used as a protection to do not flip outside the mask
729 2         8 $stat[ST_MODE] |= ( $opts->{perms} & ~_S_IFMT );
730             }
731              
732             # deal with UID / GID
733 26 100       54 if ( defined $opts->{uid} ) {
734 2 100       21 if ( $opts->{uid} =~ qr{^[0-9]+$} ) {
735 1         10 $stat[ST_UID] = $opts->{uid};
736             }
737             else {
738              
739 1         55 $stat[ST_UID] = getpwnam( $opts->{uid} );
740             }
741             }
742              
743 26 100       55 if ( defined $opts->{gid} ) {
744 2 100       11 if ( $opts->{gid} =~ qr{^[0-9]+$} ) {
745 1         2 $stat[ST_GID] = $opts->{gid};
746             }
747             else {
748 1         24 $stat[ST_GID] = getgrnam( $opts->{gid} );
749             }
750             }
751              
752             # options that we can simply copy to a slot
753 26         111 my %name2ix = (
754             size => ST_SIZE,
755             atime => ST_ATIME,
756             mtime => ST_MTIME,
757             ctime => ST_CTIME,
758             blksize => ST_BLKSIZE,
759             blocks => ST_BLOCKS,
760             );
761              
762 26         87 foreach my $k ( keys %$opts ) {
763 20         43 $k = lc($k);
764 20         40 $k =~ s{^st_}{};
765 20 100       47 next unless defined $name2ix{$k};
766              
767 14         38 $stat[ $name2ix{$k} ] = $opts->{$k};
768             }
769              
770 26         187 return \@stat;
771             }
772              
773             1;
774              
775             __END__