File Coverage

FileCheck.xs
Criterion Covered Total %
statement 219 239 91.6
branch 225 388 57.9
condition n/a
subroutine n/a
pod n/a
total 444 627 70.8


line stmt bran cond sub pod time code
1             /*
2             *
3             * Copyright (c) 2018, cPanel, LLC.
4             * All rights reserved.
5             * http://cpanel.net
6             *
7             * This is free software; you can redistribute it and/or modify it under the
8             * same terms as Perl itself.
9             *
10             */
11              
12             #include
13             #include
14             #include
15             #include
16              
17             #include
18              
19             #include "FileCheck.h"
20              
21             /* Per-interpreter data for ithreads safety.
22             * Under ithreads each interpreter gets its own copy of this struct,
23             * so mock state in one thread cannot race with another. */
24             typedef struct {
25             OverloadFTOps *overload_ft;
26             int debug;
27             } my_cxt_t;
28              
29             START_MY_CXT
30              
31             /* Convenience aliases — require dMY_CXT in calling scope */
32             #define gl_overload_ft (MY_CXT.overload_ft)
33             #define gl_debug (MY_CXT.debug)
34              
35             #define OFC_DEBUG(...) STMT_START { if (gl_debug) PerlIO_printf(PerlIO_stderr(), __VA_ARGS__); } STMT_END
36              
37             /* Macros to simplify OP overloading */
38              
39             /* generic macro with args */
40             #define _CALL_REAL_PP(zOP) (* ( gl_overload_ft->op[zOP].real_pp ) )(aTHX)
41             #define _RETURN_CALL_REAL_PP_IF_UNMOCK(zOP) if (!gl_overload_ft->op[zOP].is_mocked) return _CALL_REAL_PP(zOP);
42              
43             /* simplified versions for our custom usage */
44             #define CALL_REAL_OP() _CALL_REAL_PP(PL_op->op_type)
45             #define RETURN_CALL_REAL_OP_IF_UNMOCK() _RETURN_CALL_REAL_PP_IF_UNMOCK(PL_op->op_type)
46              
47             #define INIT_FILECHECK_MOCK(op_name, op_type, f) \
48             newCONSTSUB(stash, op_name, newSViv(op_type) ); \
49             gl_overload_ft->op[op_type].real_pp = PL_ppaddr[op_type]; \
50             PL_ppaddr[op_type] = f;
51              
52             #define RETURN_CALL_REAL_OP_IF_CALL_WITH_DEFGV() STMT_START { \
53             if (gl_overload_ft->op[OP_STAT].is_mocked) { \
54             SV *arg = *PL_stack_sp; GV *gv; \
55             if ( SvTYPE(arg) == SVt_PVAV ) arg = arg + AvMAX( arg ); \
56             if ( PL_op->op_flags & OPf_REF ) \
57             gv = cGVOP_gv; \
58             else { \
59             gv = MAYBE_DEREF_GV(arg); \
60             } \
61             OFC_DEBUG("DEFGV check: arg flags=%lu stack_sp=%p gv=%p defgv=%p\n", \
62             (unsigned long)SvFLAGS(arg), (void*)*PL_stack_sp, (void*)gv, (void*)PL_defgv); \
63             if ( SvTYPE(arg) == SVt_NULL || gv == PL_defgv ) { \
64             return CALL_REAL_OP(); \
65             } \
66             } \
67             } STMT_END
68              
69             /* a Stat_t struct has 13 elements */
70             #define STAT_T_MAX 13
71              
72             /*
73             * common helper to callback the pure perl function Overload::FileCheck::_check
74             * and get the mocked value for the -X check
75             *
76             * 1 check is true -> OP returns Yes
77             * 0 check is false -> OP returns No
78             * -2 check is null -> OP returns undef (CHECK_IS_NULL)
79             * -1 fallback to the original OP
80             */
81 1031           int _overload_ft_ops(pTHX) {
82             dMY_CXT;
83 1031           SV *const arg = *PL_stack_sp;
84 1031           int optype = PL_op->op_type; /* this is the current op_type we are mocking */
85 1031           int check_status = -1; /* 1 -> YES ; 0 -> FALSE ; -1 -> delegate */
86             int count;
87              
88 1031           dSP;
89              
90 1031           ENTER;
91 1031           SAVETMPS;
92              
93 1031 50         PUSHMARK(SP);
94 1031 50         EXTEND(SP, 2);
95 1031           PUSHs(sv_2mortal(newSViv(optype)));
96 1031           PUSHs(arg);
97              
98 1031           PUTBACK;
99              
100 1031           count = call_pv("Overload::FileCheck::_check", G_SCALAR);
101              
102 1031           SPAGAIN;
103              
104 1031 50         if (count != 1)
105 0           croak("No return value from Overload::FileCheck::_check for OP #%d\n", optype);
106              
107             {
108 1031           SV *result_sv = POPs;
109 1031 100         if (!SvOK(result_sv))
110 41           check_status = -2; /* undef => CHECK_IS_NULL */
111             else
112 990           check_status = SvIV(result_sv);
113             }
114              
115 1031 50         OFC_DEBUG("_overload_ft_ops: result=%d optype=%d\n", check_status, optype);
116              
117 1031 50         LEAVE_PRESERVING_ERRNO();
118              
119 1031           return check_status;
120             }
121              
122             /*
123             * NV-specific helper for -M, -C, -A ops.
124             *
125             * _check() returns a (status, value) pair for NV ops to avoid the -1
126             * sentinel collision: FALLBACK_TO_REAL_OP is -1, but -1.0 is a valid
127             * NV result (file modified exactly 1 day in the future).
128             *
129             * Returns:
130             * *status_out = -1 -> FALLBACK_TO_REAL_OP (nv_out is unused)
131             * *status_out = -2 -> CHECK_IS_NULL / undef (nv_out is unused)
132             * *status_out = 1 -> success, *nv_out has the value
133             */
134 100           void _overload_ft_ops_nv(pTHX_ int *status_out, NV *nv_out) {
135             dMY_CXT;
136 100           SV *const arg = *PL_stack_sp;
137 100           int optype = PL_op->op_type;
138             int count;
139              
140 100           dSP;
141              
142 100           ENTER;
143 100           SAVETMPS;
144              
145 100 50         PUSHMARK(SP);
146 100 50         EXTEND(SP, 2);
147 100           PUSHs(sv_2mortal(newSViv(optype)));
148 100           PUSHs(arg);
149              
150 100           PUTBACK;
151              
152 100           count = call_pv("Overload::FileCheck::_check", G_ARRAY);
153              
154 100           SPAGAIN;
155              
156 100 50         if (count < 1)
157 0           croak("No return value from Overload::FileCheck::_check for OP #%d\n", optype);
158              
159 100 100         if (count == 1) {
160             /* Single return: FALLBACK_TO_REAL_OP or CHECK_IS_NULL */
161 32           SV *sv = POPs;
162 32 100         if (!SvOK(sv))
163 6           *status_out = -2; /* undef => CHECK_IS_NULL */
164             else
165 26           *status_out = SvIV(sv); /* -1 for FALLBACK, -2 for NULL */
166 32           *nv_out = 0;
167             }
168 68 50         else if (count == 2) {
169             /* Pair return: (status_code, nv_value) */
170 68           SV *value_sv = POPs;
171 68           SV *status_sv = POPs;
172 68           *status_out = SvIV(status_sv);
173 68           *nv_out = SvNV(value_sv);
174             }
175             else {
176             /* Pop excess values to avoid stack corruption */
177 0           int orig_count = count;
178 0 0         while (count-- > 0) (void)POPs;
179 0           croak("Overload::FileCheck::_check returned %d values for NV OP #%d, expected 1 or 2\n", orig_count, optype);
180             }
181              
182 100 50         OFC_DEBUG("_overload_ft_ops_nv: status=%d optype=%d\n", *status_out, optype);
183              
184 100 50         LEAVE_PRESERVING_ERRNO();
185 100           }
186              
187             /*
188             * view perldoc to call SVs, method, ...
189             *
190             * https://perldoc.perl.org/perlcall.html
191             *
192             * but also https://perldoc.perl.org/perlguts.html
193             */
194              
195             #define set_stat_from_aryix(st, ix) \
196             rsv = ary[ix]; \
197             if (SvROK(rsv)) croak("Overload::FileCheck - Item %d should not be one RV\n", ix); \
198             if (SvIOK(rsv)) st = SvIV( rsv ); \
199             else if (SvUOK(rsv)) st = SvUV( rsv ); \
200             else if (SvNOK(rsv)) st = SvNV( rsv ); \
201             else if (SvPOK(rsv) && looks_like_number(rsv)) st = SvNV( rsv ); \
202             else croak("Overload::FileCheck - Item %d is not numeric...\n", ix);
203              
204              
205             /*
206             * similar to _overload_ft_ops but expect more args from _check
207             * which returns values for a fake stat
208             *
209             * Note: we could also call a dedicated function as _check_stat
210             */
211 397           int _overload_ft_stat(pTHX_ Stat_t *stat, int *size) {
212             dMY_CXT;
213 397           SV *const arg = *PL_stack_sp;
214 397           int optype = PL_op->op_type; /* this is the current op_type we are mocking */
215 397           int check_status = -1; /* 1 -> YES ; 0 -> FALSE ; -1 -> delegate */
216              
217 397           dSP;
218             int count;
219             SV *sv;
220              
221 397           ENTER;
222 397           SAVETMPS;
223              
224 397 50         PUSHMARK(SP);
225 397 50         EXTEND(SP, 2);
226 397           PUSHs(sv_2mortal(newSViv(optype)));
227 397           PUSHs(arg);
228 397           PUTBACK;
229              
230 397           count = call_pv("Overload::FileCheck::_check", G_ARRAY);
231              
232 389           SPAGAIN;
233              
234 389 50         if (count < 1)
235 0           croak("Overload::FileCheck::_check for stat OP #%d should return at least one SV.\n", optype);
236 389 50         if (count > 2)
237 0           croak("Overload::FileCheck::_check for stat OP #%d should return no more than two SVs.\n", optype);
238              
239             /* popping the stack from last entry to first */
240 389 100         if (count == 2) sv = POPs; /* RvAV */
241 389           check_status = POPi;
242              
243 389           *size = -1; /* by default it fails */
244              
245 389 100         if ( check_status == 1 ) {
246             AV *stat_array;
247             SV **ary;
248             SV *rsv;
249             int av_size;
250              
251 377 50         if (count != 2)
252 0           croak("Overload::FileCheck::_check for stat OP #%d should return two SVs on success.\n", optype);
253              
254 377 50         if ( ! SvROK(sv) )
255 0           croak( "Overload::FileCheck::_check need to return an array ref" );
256              
257 377           stat_array = MUTABLE_AV( SvRV( sv ) );
258 377 50         if ( SvTYPE(stat_array) != SVt_PVAV )
259 0           croak( "Overload::FileCheck::_check need to return an array ref" );
260              
261 377 50         av_size = AvFILL(stat_array);
262 377 100         if ( av_size >= 0 && av_size != ( STAT_T_MAX - 1 ) )
    50          
263 0           croak( "Overload::FileCheck::_check: Array should contain 0 or 13 elements, got %d", av_size + 1 );
264              
265 377           *size = av_size; /* store the av_size */
266 377 100         if ( av_size > 0 ) {
267              
268 368           ary = AvARRAY(stat_array);
269              
270             /* fill the stat struct */
271 368 50         set_stat_from_aryix( stat->st_dev, 0 ); /* IV */
    100          
    50          
    50          
    50          
    100          
272 364 50         set_stat_from_aryix( stat->st_ino, 1 ); /* IV or UV : neg = PL_statcache.st_ino < 0 */
    100          
    50          
    50          
    50          
    50          
273 364 50         set_stat_from_aryix( stat->st_mode, 2 ); /* UV */
    100          
    50          
    50          
    50          
    50          
274 364 50         set_stat_from_aryix( stat->st_nlink, 3 ); /* UV */
    100          
    50          
    50          
    50          
    50          
275 364 50         set_stat_from_aryix( stat->st_uid, 4 ); /* IV ? */
    100          
    50          
    50          
    50          
    50          
276 364 50         set_stat_from_aryix( stat->st_gid, 5 ); /* IV ? */
    100          
    50          
    50          
    50          
    50          
277 364 50         set_stat_from_aryix( stat->st_rdev, 6 ); /* IV or PV */
    100          
    50          
    50          
    50          
    50          
278 364 50         set_stat_from_aryix( stat->st_size, 7 ); /* NV or IV */
    100          
    50          
    50          
    50          
    50          
279 364 50         set_stat_from_aryix( stat->st_atime, 8 ); /* NV or IV */
    100          
    50          
    50          
    50          
    50          
280 364 50         set_stat_from_aryix( stat->st_mtime, 9 ); /* NV or IV */
    100          
    50          
    50          
    50          
    50          
281 364 50         set_stat_from_aryix( stat->st_ctime, 10 ); /* NV or IV */
    100          
    50          
    50          
    50          
    50          
282 364 50         set_stat_from_aryix( stat->st_blksize, 11 ); /* UV or PV */
    100          
    50          
    50          
    50          
    50          
283 364 50         set_stat_from_aryix( stat->st_blocks, 12 ); /* UV or PV */
    100          
    50          
    50          
    50          
    100          
284             }
285              
286             }
287              
288 383 50         LEAVE_PRESERVING_ERRNO();
289              
290 383           return check_status;
291             }
292              
293              
294             /* a generic OP to overload the FT OPs returning yes or no */
295 1928           PP(pp_overload_ft_yes_no) {
296             dMY_CXT;
297             int check_status;
298              
299 1928 50         if (!gl_overload_ft)
300 0           croak("Overload::FileCheck: internal state not initialized (gl_overload_ft is NULL)");
301              
302             /* not currently mocked */
303 1928 100         RETURN_CALL_REAL_OP_IF_UNMOCK();
304 1019 100         RETURN_CALL_REAL_OP_IF_CALL_WITH_DEFGV();
    50          
    100          
    50          
    0          
    0          
    100          
    50          
    50          
    50          
    0          
    50          
    50          
    100          
    100          
305              
306 985           check_status = _overload_ft_ops(aTHX);
307              
308             {
309             FT_SETUP_dSP_IF_NEEDED;
310              
311 985 100         if ( check_status == 1 ) FT_RETURNYES;
312 499 100         if ( check_status == 0 ) FT_RETURNNO;
313 236 100         if ( check_status == -2 ) FT_RETURNUNDEF; /* CHECK_IS_NULL */
314             }
315              
316             /* fallback */
317 197           return CALL_REAL_OP();
318             }
319              
320 84           PP(pp_overload_ft_int) {
321             dMY_CXT;
322             int check_status;
323             int saved_errno;
324              
325 84 50         if (!gl_overload_ft)
326 0           croak("Overload::FileCheck: internal state not initialized (gl_overload_ft is NULL)");
327              
328             /* not currently mocked */
329 84 100         RETURN_CALL_REAL_OP_IF_UNMOCK();
330 53 100         RETURN_CALL_REAL_OP_IF_CALL_WITH_DEFGV();
    100          
    100          
    50          
    0          
    0          
    50          
    0          
    0          
    0          
    0          
    0          
    50          
    100          
    100          
331              
332 46           check_status = _overload_ft_ops(aTHX);
333              
334 46 100         if ( check_status == -1 )
335 8           return CALL_REAL_OP();
336              
337 38 100         if ( check_status == -2 ) { /* CHECK_IS_NULL */
338             FT_SETUP_dSP_IF_NEEDED;
339 2           FT_RETURNUNDEF;
340             }
341              
342             /* Save errno — sv_setiv() and FT_RETURN_TARG can trigger allocations
343             * or other Perl internals that clobber errno. */
344 36           saved_errno = errno;
345              
346             {
347 36           dTARGET;
348             FT_SETUP_dSP_IF_NEEDED;
349              
350 36           sv_setiv(TARG, (IV) check_status);
351 36           errno = saved_errno;
352 36           FT_RETURN_TARG;
353             }
354             }
355              
356 184           PP(pp_overload_ft_nv) {
357             dMY_CXT;
358             int check_status;
359             NV nv_value;
360             int saved_errno;
361              
362 184 50         if (!gl_overload_ft)
363 0           croak("Overload::FileCheck: internal state not initialized (gl_overload_ft is NULL)");
364              
365             /* not currently mocked */
366 184 100         RETURN_CALL_REAL_OP_IF_UNMOCK();
367 100 100         RETURN_CALL_REAL_OP_IF_CALL_WITH_DEFGV();
    50          
    50          
    50          
    0          
    0          
    50          
    0          
    0          
    0          
    0          
    0          
    50          
    50          
    50          
368              
369             /* _overload_ft_ops_nv uses G_ARRAY and a status code to avoid the -1
370             * sentinel collision: FALLBACK_TO_REAL_OP is -1, but -1.0 is a valid
371             * NV result (e.g. file modified exactly 1 day in the future). */
372 100           _overload_ft_ops_nv(aTHX_ &check_status, &nv_value);
373              
374 100 100         if ( check_status == -1 )
375 26           return CALL_REAL_OP();
376              
377 74 100         if ( check_status == -2 ) { /* CHECK_IS_NULL */
378             FT_SETUP_dSP_IF_NEEDED;
379 6           FT_RETURNUNDEF;
380             }
381              
382             /* Save errno — sv_setnv() and FT_RETURN_TARG can trigger allocations
383             * or other Perl internals that clobber errno. */
384 68           saved_errno = errno;
385              
386             {
387 68           dTARGET;
388             FT_SETUP_dSP_IF_NEEDED;
389              
390 68           sv_setnv(TARG, nv_value);
391 68           errno = saved_errno;
392 68           FT_RETURN_TARG;
393             }
394             }
395              
396 501           PP(pp_overload_stat) { /* stat & lstat */
397             dMY_CXT;
398 501           Stat_t mocked_stat = { 0 }; /* fake stats */
399 501           int check_status = 0;
400             int size;
401              
402              
403 501 50         if (!gl_overload_ft)
404 0           croak("Overload::FileCheck: internal state not initialized (gl_overload_ft is NULL)");
405              
406             /* not currently mocked */
407 501 100         RETURN_CALL_REAL_OP_IF_UNMOCK();
408 403 50         RETURN_CALL_REAL_OP_IF_CALL_WITH_DEFGV();
    50          
    100          
    100          
    50          
    0          
    100          
    50          
    50          
    50          
    0          
    50          
    50          
    100          
    100          
409              
410             /* calling with our own tmp stat struct, instead of passing directly PL_statcache: more control */
411 397           check_status = _overload_ft_stat(aTHX_ &mocked_stat, &size);
412              
413             /* explicit ask for fallback */
414 383 100         if ( check_status == -1 )
415 3           return CALL_REAL_OP();
416              
417             /*
418             * The idea is too fool the stat function
419             * like if it was called by passing _ or *_
420             *
421             * We are setting these values as if stat was previously called
422             * - PL_laststype
423             * - PL_statcache
424             * - PL_laststatval
425             * - PL_statname
426             *
427             */
428              
429             {
430 380           dSP;
431              
432             /* drop & replace our stack first element with *_ */
433 380           SV *previous_stack = POPs;
434              
435             /* copy the content of mocked_stat to PL_statcache */
436 380           memcpy(&PL_statcache, &mocked_stat, sizeof(PL_statcache));
437              
438 380 100         if ( size >= 0) { /* yes it succeeds */
439 362           PL_laststatval = 0;
440             } else { /* the stat call fails */
441 18           PL_laststatval = -1;
442             }
443              
444 380           PL_laststype = PL_op->op_type; /* this was for our OP */
445              
446             /* Here, we cut early when stat() returned no values
447             * In such a case, we set the statcache, but do not call
448             * the real op (CALL_REAL_OP)
449             */
450 380 100         if ( size < 0 )
451 18           RETURN;
452              
453 362           PUSHs( MUTABLE_SV( PL_defgv ) ); /* add *_ to the stack */
454              
455             /* probably not real necesseary, make warning messages nicer */
456 362 50         if ( previous_stack && SvPOK(previous_stack) )
    100          
457 359           sv_setpv(PL_statname, SvPV_nolen(previous_stack) );
458              
459 362           return CALL_REAL_OP();
460             }
461              
462             }
463              
464             /*
465             * extract from https://perldoc.perl.org/functions/-X.html
466             *
467             * -r File is readable by effective uid/gid.
468             * -w File is writable by effective uid/gid.
469             * -x File is executable by effective uid/gid.
470             * -o File is owned by effective uid.
471             * -R File is readable by real uid/gid.
472             * -W File is writable by real uid/gid.
473             * -X File is executable by real uid/gid.
474             * -O File is owned by real uid.
475             * -e File exists.
476             * -z File has zero size (is empty).
477             * -s File has nonzero size (returns size in bytes).
478             * -f File is a plain file.
479             * -d File is a directory.
480             * -l File is a symbolic link (false if symlinks aren't
481             * supported by the file system).
482             * -p File is a named pipe (FIFO), or Filehandle is a pipe.
483             * -S File is a socket.
484             * -b File is a block special file.
485             * -c File is a character special file.
486             * -t Filehandle is opened to a tty.
487             * -u File has setuid bit set.
488             * -g File has setgid bit set.
489             * -k File has sticky bit set.
490             * -T File is an ASCII or UTF-8 text file (heuristic guess).
491             * -B File is a "binary" file (opposite of -T).
492             * -M Script start time minus file modification time, in days.
493             * -A Same for access time.
494             * -C Same for inode change time
495             */
496              
497             MODULE = Overload__FileCheck PACKAGE = Overload::FileCheck
498              
499             SV*
500             mock_op(optype)
501             SV* optype;
502             ALIAS:
503             Overload::FileCheck::_xs_mock_op = 1
504             Overload::FileCheck::_xs_unmock_op = 2
505             CODE:
506             {
507             dMY_CXT;
508 2217           int opid = 0;
509              
510 2217 50         if ( ! SvIOK(optype) )
511 0           croak("first argument to _xs_mock_op / _xs_unmock_op must be one integer");
512              
513 2217           opid = SvIV( optype );
514 2217 50         if ( !opid || opid < 0 || opid >= OP_MAX )
    50          
    50          
515 0           croak( "Invalid opid value %d", opid );
516              
517 2217           switch (ix) {
518 1288           case 1: /* _xs_mock_op */
519 1288           gl_overload_ft->op[opid].is_mocked = 1;
520 1288           break;
521 929           case 2: /* _xs_unmock_op */
522 929           gl_overload_ft->op[opid].is_mocked = 0;
523 929           break;
524 0           default:
525 0           croak("Unsupported function at index %d", ix);
526             XSRETURN_EMPTY;
527             }
528              
529 2217           XSRETURN_EMPTY;
530             }
531             OUTPUT:
532             RETVAL
533              
534              
535             SV*
536             get_basetime()
537             CODE:
538 30           RETVAL = newSViv(PL_basetime);
539             OUTPUT:
540             RETVAL
541              
542              
543             BOOT:
544             {
545             HV *stash;
546             SV *sv;
547 64           int ix = 0;
548             const char *debug_env;
549              
550             MY_CXT_INIT;
551 64           Newxz( gl_overload_ft, 1, OverloadFTOps);
552              
553 64           debug_env = getenv("OVERLOAD_FILECHECK_DEBUG");
554 64 50         if (debug_env && *debug_env && *debug_env != '0')
    0          
    0          
555 0           gl_debug = 1;
556              
557 64           stash = gv_stashpvn("Overload::FileCheck", 19, TRUE);
558              
559 64           newCONSTSUB(stash, "_loaded", newSViv(1) );
560              
561             /* provide constants to standardize return values from mocked functions */
562 64           newCONSTSUB(stash, "CHECK_IS_TRUE", &PL_sv_yes ); /* could use newSViv(1) or &PL_sv_yes */
563 64           newCONSTSUB(stash, "CHECK_IS_FALSE", &PL_sv_no ); /* could use newSViv(0) or &PL_sv_no */
564 64           newCONSTSUB(stash, "CHECK_IS_NULL", &PL_sv_undef );
565 64           newCONSTSUB(stash, "FALLBACK_TO_REAL_OP", newSVnv(-1) );
566              
567             /* provide constants to add entry in a fake stat array */
568              
569 64           newCONSTSUB(stash, "ST_DEV", newSViv(ix++) );
570 64           newCONSTSUB(stash, "ST_INO", newSViv(ix++) );
571 64           newCONSTSUB(stash, "ST_MODE", newSViv(ix++) );
572 64           newCONSTSUB(stash, "ST_NLINK", newSViv(ix++) );
573 64           newCONSTSUB(stash, "ST_UID", newSViv(ix++) );
574 64           newCONSTSUB(stash, "ST_GID", newSViv(ix++) );
575 64           newCONSTSUB(stash, "ST_RDEV", newSViv(ix++) );
576 64           newCONSTSUB(stash, "ST_SIZE", newSViv(ix++) );
577 64           newCONSTSUB(stash, "ST_ATIME", newSViv(ix++) );
578 64           newCONSTSUB(stash, "ST_MTIME", newSViv(ix++) );
579 64           newCONSTSUB(stash, "ST_CTIME", newSViv(ix++) );
580 64           newCONSTSUB(stash, "ST_BLKSIZE", newSViv(ix++) );
581 64           newCONSTSUB(stash, "ST_BLOCKS", newSViv(ix++) );
582             assert(STAT_T_MAX == ix);
583 64           newCONSTSUB(stash, "STAT_T_MAX", newSViv(STAT_T_MAX) );
584              
585             /* copy the original OP then plug our own custom OP function */
586             /* view pp_sys.c for complete list */
587              
588             /* PP(pp_ftrread) - yes/no/undef */
589 64           INIT_FILECHECK_MOCK( "OP_FTRREAD", OP_FTRREAD, &Perl_pp_overload_ft_yes_no); /* -R */
590 64           INIT_FILECHECK_MOCK( "OP_FTRWRITE", OP_FTRWRITE, &Perl_pp_overload_ft_yes_no); /* -W */
591 64           INIT_FILECHECK_MOCK( "OP_FTREXEC", OP_FTREXEC, &Perl_pp_overload_ft_yes_no); /* -X */
592 64           INIT_FILECHECK_MOCK( "OP_FTEREAD", OP_FTEREAD, &Perl_pp_overload_ft_yes_no); /* -r */
593 64           INIT_FILECHECK_MOCK( "OP_FTEWRITE", OP_FTEWRITE, &Perl_pp_overload_ft_yes_no); /* -w */
594 64           INIT_FILECHECK_MOCK( "OP_FTEEXEC", OP_FTEEXEC, &Perl_pp_overload_ft_yes_no); /* -x */
595              
596             /* PP(pp_ftis) - yes/undef/true/false */
597 64           INIT_FILECHECK_MOCK( "OP_FTIS", OP_FTIS, &Perl_pp_overload_ft_yes_no); /* -e */
598 64           INIT_FILECHECK_MOCK( "OP_FTSIZE", OP_FTSIZE, &Perl_pp_overload_ft_int); /* -s */
599 64           INIT_FILECHECK_MOCK( "OP_FTMTIME", OP_FTMTIME, &Perl_pp_overload_ft_nv); /* -M */
600 64           INIT_FILECHECK_MOCK( "OP_FTCTIME", OP_FTCTIME, &Perl_pp_overload_ft_nv); /* -C */
601 64           INIT_FILECHECK_MOCK( "OP_FTATIME", OP_FTATIME, &Perl_pp_overload_ft_nv); /* -A */
602              
603             /* PP(pp_ftrowned) yes/no/undef */
604 64           INIT_FILECHECK_MOCK( "OP_FTROWNED", OP_FTROWNED, &Perl_pp_overload_ft_yes_no); /* -O */
605 64           INIT_FILECHECK_MOCK( "OP_FTEOWNED", OP_FTEOWNED, &Perl_pp_overload_ft_yes_no); /* -o */
606 64           INIT_FILECHECK_MOCK( "OP_FTZERO", OP_FTZERO, &Perl_pp_overload_ft_yes_no); /* -z */
607 64           INIT_FILECHECK_MOCK( "OP_FTSOCK", OP_FTSOCK, &Perl_pp_overload_ft_yes_no); /* -S */
608 64           INIT_FILECHECK_MOCK( "OP_FTCHR", OP_FTCHR, &Perl_pp_overload_ft_yes_no); /* -c */
609 64           INIT_FILECHECK_MOCK( "OP_FTBLK", OP_FTBLK, &Perl_pp_overload_ft_yes_no); /* -b */
610 64           INIT_FILECHECK_MOCK( "OP_FTFILE", OP_FTFILE, &Perl_pp_overload_ft_yes_no); /* -f */
611 64           INIT_FILECHECK_MOCK( "OP_FTDIR", OP_FTDIR, &Perl_pp_overload_ft_yes_no); /* -d */
612 64           INIT_FILECHECK_MOCK( "OP_FTPIPE", OP_FTPIPE, &Perl_pp_overload_ft_yes_no); /* -p */
613 64           INIT_FILECHECK_MOCK( "OP_FTSUID", OP_FTSUID, &Perl_pp_overload_ft_yes_no); /* -u */
614 64           INIT_FILECHECK_MOCK( "OP_FTSGID", OP_FTSGID, &Perl_pp_overload_ft_yes_no); /* -g */
615 64           INIT_FILECHECK_MOCK( "OP_FTSVTX", OP_FTSVTX, &Perl_pp_overload_ft_yes_no); /* -k */
616              
617             /* PP(pp_ftlink) - yes/no/undef */
618 64           INIT_FILECHECK_MOCK( "OP_FTLINK", OP_FTLINK, &Perl_pp_overload_ft_yes_no); /* -l */
619              
620             /* PP(pp_fttty) - yes/no/undef */
621 64           INIT_FILECHECK_MOCK( "OP_FTTTY", OP_FTTTY, &Perl_pp_overload_ft_yes_no); /* -t */
622              
623             /* PP(pp_fttext) - yes/no/undef */
624 64           INIT_FILECHECK_MOCK( "OP_FTTEXT", OP_FTTEXT, &Perl_pp_overload_ft_yes_no); /* -T */
625 64           INIT_FILECHECK_MOCK( "OP_FTBINARY", OP_FTBINARY, &Perl_pp_overload_ft_yes_no); /* -B */
626              
627             /* PP(pp_stat) also used for: pp_lstat() */
628 64           INIT_FILECHECK_MOCK( "OP_STAT", OP_STAT, &Perl_pp_overload_stat); /* stat */
629 64           INIT_FILECHECK_MOCK( "OP_LSTAT", OP_LSTAT, &Perl_pp_overload_stat); /* lstat */
630              
631             }
632              
633             #ifdef USE_ITHREADS
634              
635             void
636             CLONE(...)
637             CODE:
638             {
639             MY_CXT_CLONE;
640             /* Parent's overload_ft pointer was shallow-copied by MY_CXT_CLONE.
641             * Allocate a fresh struct for the child interpreter: copy the saved
642             * real_pp pointers (they're the same per-process) but start with
643             * all ops unmocked — each thread manages its own mock state. */
644             {
645             OverloadFTOps *parent_ft = gl_overload_ft;
646             int i;
647             Newxz(gl_overload_ft, 1, OverloadFTOps);
648             for (i = 0; i < OP_MAX; i++) {
649             gl_overload_ft->op[i].real_pp = parent_ft->op[i].real_pp;
650             /* is_mocked stays 0 from Newxz */
651             }
652             }
653             }
654              
655             #endif
656