File Coverage

file.c
Criterion Covered Total %
statement 1890 2446 77.2
branch 739 1332 55.4
condition n/a
subroutine n/a
pod n/a
total 2629 3778 69.5


line stmt bran cond sub pod time code
1             /*
2             * file.c - Fast IO operations using direct system calls
3             *
4             * Features:
5             * - slurp/spew with minimal overhead
6             * - Memory-mapped file access (mmap)
7             * - Efficient line iteration
8             * - Direct stat access
9             * - Windows and POSIX support
10             */
11              
12             #define PERL_NO_GET_CONTEXT
13             #include "EXTERN.h"
14             #include "perl.h"
15             #include "XSUB.h"
16             #include "include/file_compat.h"
17             #include "include/file_hooks.h"
18              
19             #include
20             #include
21             #include
22             #include
23              
24             #ifdef _WIN32
25             #include
26             #include
27             #include
28             /*
29             * Windows compatibility - use Perl's wrapper functions
30             * We DON'T redefine open/read/write/close/stat/fstat/access here
31             * because Perl's XSUB.h already defines them to work correctly.
32             * Just define the flags and other missing bits.
33             */
34             #define O_RDONLY _O_RDONLY
35             #define O_WRONLY _O_WRONLY
36             #define O_RDWR _O_RDWR
37             #define O_CREAT _O_CREAT
38             #define O_TRUNC _O_TRUNC
39             #define O_APPEND _O_APPEND
40             #define O_BINARY _O_BINARY
41             #ifndef S_ISREG
42             #define S_ISREG(m) (((m) & _S_IFMT) == _S_IFREG)
43             #endif
44             #ifndef S_ISDIR
45             #define S_ISDIR(m) (((m) & _S_IFMT) == _S_IFDIR)
46             #endif
47             #define R_OK 4
48             #define W_OK 2
49             /* ssize_t for Windows */
50             #ifndef ssize_t
51             #ifdef _WIN64
52             typedef __int64 ssize_t;
53             #else
54             typedef int ssize_t;
55             #endif
56             #endif
57             /* Windows doesn't have real uid/gid - use dummy values */
58             #define FILE_FAKE_UID 1000
59             #define FILE_FAKE_GID 1000
60             /*
61             * On Windows with PERL_IMPLICIT_SYS, Perl redefines open() to
62             * PerlLIO_open() which only accepts 2 args. Use _open() directly
63             * for the 3-arg form (path, flags, mode).
64             */
65             #define file_open3(path, flags, mode) _open(path, flags, mode)
66             #else
67             #define file_open3(path, flags, mode) open(path, flags, mode)
68             #include
69             #include
70             #include /* For utime - more portable than utimes */
71             #include /* For readdir */
72             #if defined(__linux__)
73             #include /* Zero-copy file transfer */
74             #endif
75             #if defined(__APPLE__)
76             #include /* macOS native file copy */
77             #endif
78             #endif
79              
80             /* Default buffer size for reads - 64KB is optimal for most systems */
81             #define FILE_BUFFER_SIZE 65536
82              
83             /* Larger buffer for bulk operations */
84             #define FILE_BULK_BUFFER_SIZE 262144
85              
86             /* Threshold for mmap-based slurp (4MB) */
87             #define MMAP_SLURP_THRESHOLD (4 * 1024 * 1024)
88              
89             /* Branch prediction hints */
90             #ifndef LIKELY
91             #if defined(__GNUC__) || defined(__clang__)
92             #define LIKELY(x) __builtin_expect(!!(x), 1)
93             #define UNLIKELY(x) __builtin_expect(!!(x), 0)
94             #else
95             #define LIKELY(x) (x)
96             #define UNLIKELY(x) (x)
97             #endif
98             #endif
99              
100             /* posix_fadvise hints for kernel optimization */
101             #if defined(__linux__) || defined(__FreeBSD__) || defined(__NetBSD__)
102             #define HAVE_POSIX_FADVISE 1
103             #define advise_sequential(fd, len) posix_fadvise(fd, 0, len, POSIX_FADV_SEQUENTIAL)
104             #define advise_dontneed(fd, len) posix_fadvise(fd, 0, len, POSIX_FADV_DONTNEED)
105             #else
106             #define HAVE_POSIX_FADVISE 0
107             #define advise_sequential(fd, len) ((void)0)
108             #define advise_dontneed(fd, len) ((void)0)
109             #endif
110              
111             /* ============================================
112             File hooks - lazy approach with simple pointer checks
113             Implementation in header so external XS modules (e.g. the test
114             extension under t/xs/) can include it directly on platforms where
115             linking against Raw.so is impractical (Windows/Cygwin without an
116             import library).
117             ============================================ */
118             #include "include/file_hooks_impl.h"
119              
120             /* ============================================
121             Stat cache - like Perl's _ special filehandle
122             ============================================ */
123             #define STAT_CACHE_PATH_MAX 1024
124              
125             static struct {
126             char path[STAT_CACHE_PATH_MAX];
127             Stat_t st;
128             int valid;
129             #ifdef _WIN32
130             int uid;
131             int gid;
132             #else
133             uid_t uid;
134             gid_t gid;
135             #endif
136             } g_stat_cache = { "", {0}, 0, 0, 0 };
137              
138             /* Get cached stat or perform new stat */
139 386           static int cached_stat(const char *path, Stat_t *st) {
140             dTHX;
141 386 100         if (g_stat_cache.valid && strcmp(path, g_stat_cache.path) == 0) {
    100          
142 148           *st = g_stat_cache.st;
143 148           return 0;
144             }
145            
146 238 100         if (stat(path, st) < 0) {
147 32           g_stat_cache.valid = 0;
148 32           return -1;
149             }
150            
151             /* Cache the result */
152 206           size_t len = strlen(path);
153 206 50         if (len < STAT_CACHE_PATH_MAX) {
154 206           memcpy(g_stat_cache.path, path, len + 1);
155 206           g_stat_cache.st = *st;
156             #ifdef _WIN32
157             /* Windows doesn't have real uid/gid concepts */
158             g_stat_cache.uid = FILE_FAKE_UID;
159             g_stat_cache.gid = FILE_FAKE_GID;
160             #else
161 206           g_stat_cache.uid = geteuid();
162 206           g_stat_cache.gid = getegid();
163             #endif
164 206           g_stat_cache.valid = 1;
165             }
166            
167 206           return 0;
168             }
169              
170             /* Invalidate cache (call after write operations) */
171 1           static void invalidate_stat_cache(void) {
172 1           g_stat_cache.valid = 0;
173 1           }
174              
175             /* Invalidate cache for specific path */
176 1           static void invalidate_stat_cache_path(const char *path) {
177 1 50         if (g_stat_cache.valid && strcmp(path, g_stat_cache.path) == 0) {
    50          
178 1           g_stat_cache.valid = 0;
179             }
180 1           }
181              
182             /* Check readable using cached stat */
183 9           static int file_is_readable_cached(const char *path) {
184             dTHX;
185             #ifdef _WIN32
186             return access(path, R_OK) == 0;
187             #else
188             Stat_t st;
189 9 100         if (cached_stat(path, &st) < 0) return 0;
190            
191 8 50         if (g_stat_cache.uid == 0) return 1; /* root can read anything */
192            
193 0 0         if (st.st_uid == g_stat_cache.uid) {
194 0           return (st.st_mode & S_IRUSR) != 0;
195 0 0         } else if (st.st_gid == g_stat_cache.gid) {
196 0           return (st.st_mode & S_IRGRP) != 0;
197             } else {
198 0           return (st.st_mode & S_IROTH) != 0;
199             }
200             #endif
201             }
202              
203             /* Check writable using cached stat */
204 9           static int file_is_writable_cached(const char *path) {
205             dTHX;
206             #ifdef _WIN32
207             return access(path, W_OK) == 0;
208             #else
209             Stat_t st;
210 9 100         if (cached_stat(path, &st) < 0) return 0;
211            
212 8 50         if (g_stat_cache.uid == 0) return 1; /* root can write anything */
213            
214 0 0         if (st.st_uid == g_stat_cache.uid) {
215 0           return (st.st_mode & S_IWUSR) != 0;
216 0 0         } else if (st.st_gid == g_stat_cache.gid) {
217 0           return (st.st_mode & S_IWGRP) != 0;
218             } else {
219 0           return (st.st_mode & S_IWOTH) != 0;
220             }
221             #endif
222             }
223              
224             /* Check executable using cached stat */
225 8           static int file_is_executable_cached(const char *path) {
226             dTHX;
227             #ifdef _WIN32
228             /* Windows: check file extension for executability */
229             const char *ext = strrchr(path, '.');
230             if (ext) {
231             if (_stricmp(ext, ".exe") == 0 || _stricmp(ext, ".bat") == 0 ||
232             _stricmp(ext, ".cmd") == 0 || _stricmp(ext, ".com") == 0) {
233             return access(path, R_OK) == 0;
234             }
235             }
236             return 0;
237             #else
238             Stat_t st;
239 8 50         if (cached_stat(path, &st) < 0) return 0;
240            
241 8 50         if (g_stat_cache.uid == 0) return 1; /* root can execute anything */
242            
243 0 0         if (st.st_uid == g_stat_cache.uid) {
244 0           return (st.st_mode & S_IXUSR) != 0;
245 0 0         } else if (st.st_gid == g_stat_cache.gid) {
246 0           return (st.st_mode & S_IXGRP) != 0;
247             } else {
248 0           return (st.st_mode & S_IXOTH) != 0;
249             }
250             #endif
251             }
252              
253              
254             /* ============================================
255             Custom op support for compile-time optimization
256             ============================================ */
257              
258             /* Custom op registrations */
259             static XOP file_slurp_xop;
260             static XOP file_spew_xop;
261             static XOP file_exists_xop;
262             static XOP file_size_xop;
263             static XOP file_is_file_xop;
264             static XOP file_is_dir_xop;
265             static XOP file_lines_xop;
266             static XOP file_unlink_xop;
267             static XOP file_mkdir_xop;
268             static XOP file_rmdir_xop;
269             static XOP file_basename_xop;
270             static XOP file_dirname_xop;
271             static XOP file_extname_xop;
272             static XOP file_touch_xop;
273             static XOP file_clear_stat_cache_xop;
274             static XOP file_mtime_xop;
275             static XOP file_atime_xop;
276             static XOP file_ctime_xop;
277             static XOP file_mode_xop;
278             static XOP file_is_link_xop;
279             static XOP file_is_readable_xop;
280             static XOP file_is_writable_xop;
281             static XOP file_is_executable_xop;
282             static XOP file_readdir_xop;
283             static XOP file_slurp_raw_xop;
284             static XOP file_copy_xop;
285             static XOP file_move_xop;
286             static XOP file_chmod_xop;
287             static XOP file_append_xop;
288             static XOP file_atomic_spew_xop;
289              
290             /* Forward declarations for internal functions */
291             static SV* file_slurp_internal(pTHX_ const char *path);
292             static SV* file_slurp_raw_internal(pTHX_ const char *path);
293             static int file_spew_internal(pTHX_ const char *path, SV *data);
294             static int file_append_internal(pTHX_ const char *path, SV *data);
295             static IV file_size_internal(const char *path);
296             static IV file_mtime_internal(const char *path);
297             static IV file_atime_internal(const char *path);
298             static IV file_ctime_internal(const char *path);
299             static IV file_mode_internal(const char *path);
300             static int file_exists_internal(const char *path);
301             static int file_is_file_internal(const char *path);
302             static int file_is_dir_internal(const char *path);
303             static int file_is_link_internal(const char *path);
304             static int file_is_readable_internal(const char *path);
305             static int file_is_writable_internal(const char *path);
306             static int file_is_executable_internal(const char *path);
307             static AV* file_split_lines(pTHX_ SV *content);
308             static int file_unlink_internal(const char *path);
309             static int file_copy_internal(pTHX_ const char *src, const char *dst);
310             static int file_move_internal(pTHX_ const char *src, const char *dst);
311             static int file_mkdir_internal(const char *path, int mode);
312             static int file_rmdir_internal(const char *path);
313             static int file_touch_internal(const char *path);
314             static int file_chmod_internal(const char *path, int mode);
315             static AV* file_readdir_internal(pTHX_ const char *path);
316             static int file_atomic_spew_internal(pTHX_ const char *path, SV *data);
317             static SV* file_basename_internal(pTHX_ const char *path);
318             static SV* file_dirname_internal(pTHX_ const char *path);
319             static SV* file_extname_internal(pTHX_ const char *path);
320              
321             /* Typedef for pp functions */
322             typedef OP* (*file_ppfunc)(pTHX);
323              
324             /* ============================================
325             Custom OP implementations - fastest path
326             ============================================ */
327              
328             /* pp_file_slurp: single path arg on stack - OPTIMIZED HOT PATH */
329 75           static OP* pp_file_slurp(pTHX) {
330 75           dSP;
331 75           SV *path_sv = POPs;
332 75           const char *path = SvPV_nolen(path_sv);
333             int fd;
334             Stat_t st;
335             SV *result;
336             char *buf;
337             ssize_t n, total;
338              
339             /* If hooks registered, use full path with hook support */
340 75 100         if (g_file_read_hook || g_file_hooks[FILE_HOOK_PHASE_READ]) {
    100          
341 9           result = file_slurp_internal(aTHX_ path);
342 9           PUSHs(sv_2mortal(result));
343 9           PUTBACK;
344 9           return NORMAL;
345             }
346              
347             /* Fast path: direct syscalls, no hooks */
348             #ifdef _WIN32
349             fd = open(path, O_RDONLY | O_BINARY);
350             #else
351 66           fd = open(path, O_RDONLY);
352             #endif
353 66 50         if (fd < 0) {
354 0           PUSHs(&PL_sv_undef);
355 0           PUTBACK;
356 0           return NORMAL;
357             }
358              
359 66 50         if (fstat(fd, &st) < 0 || !S_ISREG(st.st_mode)) {
    50          
360 0           close(fd);
361 0           PUSHs(&PL_sv_undef);
362 0           PUTBACK;
363 0           return NORMAL;
364             }
365              
366             /* Empty file */
367 66 100         if (st.st_size == 0) {
368 1           close(fd);
369 1           result = newSVpvs("");
370 1           PUSHs(sv_2mortal(result));
371 1           PUTBACK;
372 1           return NORMAL;
373             }
374              
375             /* Hint to kernel: sequential read */
376 65           advise_sequential(fd, st.st_size);
377              
378             /* Pre-allocate exact size */
379 65           result = newSV(st.st_size + 1);
380 65           SvPOK_on(result);
381 65           buf = SvPVX(result);
382              
383             #ifndef _WIN32
384             /* Large files: use mmap for zero-copy */
385 65 50         if (st.st_size >= MMAP_SLURP_THRESHOLD) {
386 0           void *map = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
387 0 0         if (map != MAP_FAILED) {
388             #ifdef MADV_SEQUENTIAL
389 0           madvise(map, st.st_size, MADV_SEQUENTIAL);
390             #endif
391 0           memcpy(buf, map, st.st_size);
392 0           buf[st.st_size] = '\0';
393 0           SvCUR_set(result, st.st_size);
394 0           munmap(map, st.st_size);
395 0           close(fd);
396 0           PUSHs(sv_2mortal(result));
397 0           PUTBACK;
398 0           return NORMAL;
399             }
400             /* mmap failed, fall through to read */
401             }
402             #endif
403              
404             /* Single read - common case for small/medium files */
405 65           n = read(fd, buf, st.st_size);
406 65 50         if (n == st.st_size) {
407             /* Got everything in one read - fast path */
408 65           close(fd);
409 65           buf[n] = '\0';
410 65           SvCUR_set(result, n);
411 65           PUSHs(sv_2mortal(result));
412 65           PUTBACK;
413 65           return NORMAL;
414             }
415            
416             /* Short read or error - need loop */
417 0 0         if (n < 0) {
418 0 0         if (errno == EINTR) {
419 0           n = 0; /* Start from beginning */
420             } else {
421 0           close(fd);
422 0           SvREFCNT_dec(result);
423 0           PUSHs(&PL_sv_undef);
424 0           PUTBACK;
425 0           return NORMAL;
426             }
427             }
428            
429 0           total = n;
430 0 0         while (total < st.st_size) {
431 0           n = read(fd, buf + total, st.st_size - total);
432 0 0         if (n < 0) {
433 0 0         if (errno == EINTR) continue;
434 0           close(fd);
435 0           SvREFCNT_dec(result);
436 0           PUSHs(&PL_sv_undef);
437 0           PUTBACK;
438 0           return NORMAL;
439             }
440 0 0         if (n == 0) break;
441 0           total += n;
442             }
443            
444 0           close(fd);
445 0           buf[total] = '\0';
446 0           SvCUR_set(result, total);
447            
448 0           PUSHs(sv_2mortal(result));
449 0           PUTBACK;
450 0           return NORMAL;
451             }
452              
453             /* pp_file_spew: path and data on stack */
454 71           static OP* pp_file_spew(pTHX) {
455 71           dSP;
456 71           SV *data = POPs;
457 71           SV *path_sv = POPs;
458 71           const char *path = SvPV_nolen(path_sv);
459 71 50         PUSHs(file_spew_internal(aTHX_ path, data) ? &PL_sv_yes : &PL_sv_no);
460 71           PUTBACK;
461 71           return NORMAL;
462             }
463              
464             /* pp_file_exists: single path arg on stack */
465 147           static OP* pp_file_exists(pTHX) {
466 147           dSP;
467 147           SV *path_sv = POPs;
468 147           const char *path = SvPV_nolen(path_sv);
469 147 100         PUSHs(file_exists_internal(path) ? &PL_sv_yes : &PL_sv_no);
470 147           PUTBACK;
471 147           return NORMAL;
472             }
473              
474             /* pp_file_size: single path arg on stack */
475 54           static OP* pp_file_size(pTHX) {
476 54           dSP;
477 54           SV *path_sv = POPs;
478 54           const char *path = SvPV_nolen(path_sv);
479 54           PUSHs(sv_2mortal(newSViv(file_size_internal(path))));
480 54           PUTBACK;
481 54           return NORMAL;
482             }
483              
484             /* pp_file_is_file: single path arg on stack */
485 29           static OP* pp_file_is_file(pTHX) {
486 29           dSP;
487 29           SV *path_sv = POPs;
488 29           const char *path = SvPV_nolen(path_sv);
489 29 100         PUSHs(file_is_file_internal(path) ? &PL_sv_yes : &PL_sv_no);
490 29           PUTBACK;
491 29           return NORMAL;
492             }
493              
494             /* pp_file_is_dir: single path arg on stack */
495 24           static OP* pp_file_is_dir(pTHX) {
496 24           dSP;
497 24           SV *path_sv = POPs;
498 24           const char *path = SvPV_nolen(path_sv);
499 24 100         PUSHs(file_is_dir_internal(path) ? &PL_sv_yes : &PL_sv_no);
500 24           PUTBACK;
501 24           return NORMAL;
502             }
503              
504             /* pp_file_lines: single path arg on stack */
505 6           static OP* pp_file_lines(pTHX) {
506 6           dSP;
507 6           SV *path_sv = POPs;
508 6           const char *path = SvPV_nolen(path_sv);
509 6           SV *content = file_slurp_internal(aTHX_ path);
510             AV *lines;
511              
512 6 50         if (content == &PL_sv_undef) {
513 0           lines = newAV();
514             } else {
515 6           lines = file_split_lines(aTHX_ content);
516 6           SvREFCNT_dec(content);
517             }
518              
519 6           PUSHs(sv_2mortal(newRV_noinc((SV*)lines)));
520 6           PUTBACK;
521 6           return NORMAL;
522             }
523              
524             /* pp_file_unlink: single path arg on stack */
525 2           static OP* pp_file_unlink(pTHX) {
526 2           dSP;
527 2           SV *path_sv = POPs;
528 2           const char *path = SvPV_nolen(path_sv);
529 2 100         PUSHs(file_unlink_internal(path) ? &PL_sv_yes : &PL_sv_no);
530 2           PUTBACK;
531 2           return NORMAL;
532             }
533              
534             /* pp_file_clear_stat_cache: optional path arg - clears stat cache */
535 1           static OP* pp_file_clear_stat_cache(pTHX) {
536 1           dSP;
537 1           SV *path_sv = POPs;
538            
539 1 50         if (SvOK(path_sv)) {
540             /* Clear cache for specific path */
541 1           const char *path = SvPV_nolen(path_sv);
542 1           invalidate_stat_cache_path(path);
543             } else {
544             /* Clear entire cache */
545 0           invalidate_stat_cache();
546             }
547            
548 1           PUSHs(&PL_sv_yes);
549 1           PUTBACK;
550 1           return NORMAL;
551             }
552              
553             /* pp_file_mkdir: single path arg on stack (mode defaults to 0755) */
554 4           static OP* pp_file_mkdir(pTHX) {
555 4           dSP;
556 4           SV *path_sv = POPs;
557 4           const char *path = SvPV_nolen(path_sv);
558 4 50         PUSHs(file_mkdir_internal(path, 0755) ? &PL_sv_yes : &PL_sv_no);
559 4           PUTBACK;
560 4           return NORMAL;
561             }
562              
563             /* pp_file_rmdir: single path arg on stack */
564 1           static OP* pp_file_rmdir(pTHX) {
565 1           dSP;
566 1           SV *path_sv = POPs;
567 1           const char *path = SvPV_nolen(path_sv);
568 1 50         PUSHs(file_rmdir_internal(path) ? &PL_sv_yes : &PL_sv_no);
569 1           PUTBACK;
570 1           return NORMAL;
571             }
572              
573             /* pp_file_touch: single path arg on stack */
574 1           static OP* pp_file_touch(pTHX) {
575 1           dSP;
576 1           SV *path_sv = POPs;
577 1           const char *path = SvPV_nolen(path_sv);
578 1 50         PUSHs(file_touch_internal(path) ? &PL_sv_yes : &PL_sv_no);
579 1           PUTBACK;
580 1           return NORMAL;
581             }
582              
583             /* pp_file_basename: single path arg on stack */
584 71           static OP* pp_file_basename(pTHX) {
585 71           dSP;
586 71           SV *path_sv = POPs;
587 71           const char *path = SvPV_nolen(path_sv);
588 71           PUSHs(sv_2mortal(file_basename_internal(aTHX_ path)));
589 71           PUTBACK;
590 71           return NORMAL;
591             }
592              
593             /* pp_file_dirname: single path arg on stack */
594 10           static OP* pp_file_dirname(pTHX) {
595 10           dSP;
596 10           SV *path_sv = POPs;
597 10           const char *path = SvPV_nolen(path_sv);
598 10           PUSHs(sv_2mortal(file_dirname_internal(aTHX_ path)));
599 10           PUTBACK;
600 10           return NORMAL;
601             }
602              
603             /* pp_file_extname: single path arg on stack */
604 36           static OP* pp_file_extname(pTHX) {
605 36           dSP;
606 36           SV *path_sv = POPs;
607 36           const char *path = SvPV_nolen(path_sv);
608 36           PUSHs(sv_2mortal(file_extname_internal(aTHX_ path)));
609 36           PUTBACK;
610 36           return NORMAL;
611             }
612              
613             /* pp_file_mtime: single path arg on stack */
614 27           static OP* pp_file_mtime(pTHX) {
615 27           dSP;
616 27           SV *path_sv = POPs;
617 27           const char *path = SvPV_nolen(path_sv);
618 27           PUSHs(sv_2mortal(newSViv(file_mtime_internal(path))));
619 27           PUTBACK;
620 27           return NORMAL;
621             }
622              
623             /* pp_file_atime: single path arg on stack */
624 6           static OP* pp_file_atime(pTHX) {
625 6           dSP;
626 6           SV *path_sv = POPs;
627 6           const char *path = SvPV_nolen(path_sv);
628 6           PUSHs(sv_2mortal(newSViv(file_atime_internal(path))));
629 6           PUTBACK;
630 6           return NORMAL;
631             }
632              
633             /* pp_file_ctime: single path arg on stack */
634 6           static OP* pp_file_ctime(pTHX) {
635 6           dSP;
636 6           SV *path_sv = POPs;
637 6           const char *path = SvPV_nolen(path_sv);
638 6           PUSHs(sv_2mortal(newSViv(file_ctime_internal(path))));
639 6           PUTBACK;
640 6           return NORMAL;
641             }
642              
643             /* pp_file_mode: single path arg on stack */
644 3           static OP* pp_file_mode(pTHX) {
645 3           dSP;
646 3           SV *path_sv = POPs;
647 3           const char *path = SvPV_nolen(path_sv);
648 3           PUSHs(sv_2mortal(newSViv(file_mode_internal(path))));
649 3           PUTBACK;
650 3           return NORMAL;
651             }
652              
653             /* pp_file_is_link: single path arg on stack */
654 6           static OP* pp_file_is_link(pTHX) {
655 6           dSP;
656 6           SV *path_sv = POPs;
657 6           const char *path = SvPV_nolen(path_sv);
658 6 100         PUSHs(file_is_link_internal(path) ? &PL_sv_yes : &PL_sv_no);
659 6           PUTBACK;
660 6           return NORMAL;
661             }
662              
663             /* pp_file_is_readable: single path arg on stack */
664 7           static OP* pp_file_is_readable(pTHX) {
665 7           dSP;
666 7           SV *path_sv = POPs;
667 7           const char *path = SvPV_nolen(path_sv);
668 7 50         PUSHs(file_is_readable_internal(path) ? &PL_sv_yes : &PL_sv_no);
669 7           PUTBACK;
670 7           return NORMAL;
671             }
672              
673             /* pp_file_is_writable: single path arg on stack */
674 6           static OP* pp_file_is_writable(pTHX) {
675 6           dSP;
676 6           SV *path_sv = POPs;
677 6           const char *path = SvPV_nolen(path_sv);
678 6 50         PUSHs(file_is_writable_internal(path) ? &PL_sv_yes : &PL_sv_no);
679 6           PUTBACK;
680 6           return NORMAL;
681             }
682              
683             /* pp_file_is_executable: single path arg on stack */
684 8           static OP* pp_file_is_executable(pTHX) {
685 8           dSP;
686 8           SV *path_sv = POPs;
687 8           const char *path = SvPV_nolen(path_sv);
688 8 50         PUSHs(file_is_executable_internal(path) ? &PL_sv_yes : &PL_sv_no);
689 8           PUTBACK;
690 8           return NORMAL;
691             }
692              
693             /* pp_file_readdir: single path arg on stack */
694 0           static OP* pp_file_readdir(pTHX) {
695 0           dSP;
696 0           SV *path_sv = POPs;
697 0           const char *path = SvPV_nolen(path_sv);
698 0           AV *result = file_readdir_internal(aTHX_ path);
699 0           PUSHs(sv_2mortal(newRV_noinc((SV*)result)));
700 0           PUTBACK;
701 0           return NORMAL;
702             }
703              
704             /* pp_file_slurp_raw: single path arg on stack (bypasses hooks) */
705 1           static OP* pp_file_slurp_raw(pTHX) {
706 1           dSP;
707 1           SV *path_sv = POPs;
708 1           const char *path = SvPV_nolen(path_sv);
709 1           SV *result = file_slurp_raw_internal(aTHX_ path);
710 1           PUSHs(sv_2mortal(result));
711 1           PUTBACK;
712 1           return NORMAL;
713             }
714              
715             /* pp_file_copy: src and dst on stack */
716 1           static OP* pp_file_copy(pTHX) {
717 1           dSP;
718 1           SV *dst_sv = POPs;
719 1           SV *src_sv = POPs;
720 1           const char *src = SvPV_nolen(src_sv);
721 1           const char *dst = SvPV_nolen(dst_sv);
722 1 50         PUSHs(file_copy_internal(aTHX_ src, dst) ? &PL_sv_yes : &PL_sv_no);
723 1           PUTBACK;
724 1           return NORMAL;
725             }
726              
727             /* pp_file_move: src and dst on stack */
728 0           static OP* pp_file_move(pTHX) {
729 0           dSP;
730 0           SV *dst_sv = POPs;
731 0           SV *src_sv = POPs;
732 0           const char *src = SvPV_nolen(src_sv);
733 0           const char *dst = SvPV_nolen(dst_sv);
734 0 0         PUSHs(file_move_internal(aTHX_ src, dst) ? &PL_sv_yes : &PL_sv_no);
735 0           PUTBACK;
736 0           return NORMAL;
737             }
738              
739             /* pp_file_chmod: path and mode on stack */
740 0           static OP* pp_file_chmod(pTHX) {
741 0           dSP;
742 0           SV *mode_sv = POPs;
743 0           SV *path_sv = POPs;
744 0           const char *path = SvPV_nolen(path_sv);
745 0           int mode = SvIV(mode_sv);
746 0 0         PUSHs(file_chmod_internal(path, mode) ? &PL_sv_yes : &PL_sv_no);
747 0           PUTBACK;
748 0           return NORMAL;
749             }
750              
751             /* pp_file_append: path and data on stack */
752 2           static OP* pp_file_append(pTHX) {
753 2           dSP;
754 2           SV *data = POPs;
755 2           SV *path_sv = POPs;
756 2           const char *path = SvPV_nolen(path_sv);
757 2 50         PUSHs(file_append_internal(aTHX_ path, data) ? &PL_sv_yes : &PL_sv_no);
758 2           PUTBACK;
759 2           return NORMAL;
760             }
761              
762             /* pp_file_atomic_spew: path and data on stack */
763 0           static OP* pp_file_atomic_spew(pTHX) {
764 0           dSP;
765 0           SV *data = POPs;
766 0           SV *path_sv = POPs;
767 0           const char *path = SvPV_nolen(path_sv);
768 0 0         PUSHs(file_atomic_spew_internal(aTHX_ path, data) ? &PL_sv_yes : &PL_sv_no);
769 0           PUTBACK;
770 0           return NORMAL;
771             }
772              
773             /* ============================================
774             Call checkers for compile-time optimization
775             ============================================ */
776              
777             /* 1-arg call checker (slurp, exists, size, is_file, is_dir, lines) */
778 170           static OP* file_call_checker_1arg(pTHX_ OP *entersubop, GV *namegv, SV *ckobj) {
779 170           file_ppfunc ppfunc = (file_ppfunc)SvIVX(ckobj);
780             OP *pushop, *cvop, *argop;
781             OP *newop;
782              
783             PERL_UNUSED_ARG(namegv);
784              
785             /* Navigate to first child */
786 170           pushop = cUNOPx(entersubop)->op_first;
787 170 50         if (!OpHAS_SIBLING(pushop)) {
788 170           pushop = cUNOPx(pushop)->op_first;
789             }
790              
791             /* Get the args: pushmark -> arg -> cv */
792 170 50         argop = OpSIBLING(pushop);
793 170 50         if (!argop) return entersubop;
794              
795 170 100         cvop = OpSIBLING(argop);
796 170 100         if (!cvop) return entersubop;
797              
798             /* Verify exactly 1 arg */
799 169 50         if (OpSIBLING(argop) != cvop) return entersubop;
    50          
800              
801             /* Detach arg from tree */
802 169           OpMORESIB_set(pushop, cvop);
803 169           OpLASTSIB_set(argop, NULL);
804              
805             /* Force scalar context so function calls return exactly one value */
806 169           argop = op_contextualize(argop, G_SCALAR);
807              
808             /* Create as OP_NULL first to avoid -DDEBUGGING assertion in newUNOP,
809             then convert to OP_CUSTOM */
810 169           newop = newUNOP(OP_NULL, 0, argop);
811 169           newop->op_type = OP_CUSTOM;
812 169           newop->op_ppaddr = ppfunc;
813              
814 169           op_free(entersubop);
815 169           return newop;
816             }
817              
818             /* 2-arg call checker (spew, append) */
819 46           static OP* file_call_checker_2arg(pTHX_ OP *entersubop, GV *namegv, SV *ckobj) {
820 46           file_ppfunc ppfunc = (file_ppfunc)SvIVX(ckobj);
821             OP *pushop, *cvop, *pathop, *dataop;
822             OP *newop;
823              
824             PERL_UNUSED_ARG(namegv);
825              
826             /* Navigate to first child */
827 46           pushop = cUNOPx(entersubop)->op_first;
828 46 50         if (!OpHAS_SIBLING(pushop)) {
829 46           pushop = cUNOPx(pushop)->op_first;
830             }
831              
832             /* Get the args: pushmark -> path -> data -> cv */
833 46 50         pathop = OpSIBLING(pushop);
834 46 50         if (!pathop) return entersubop;
835              
836 46 50         dataop = OpSIBLING(pathop);
837 46 50         if (!dataop) return entersubop;
838              
839 46 50         cvop = OpSIBLING(dataop);
840 46 50         if (!cvop) return entersubop;
841              
842             /* Verify exactly 2 args */
843 46 50         if (OpSIBLING(dataop) != cvop) return entersubop;
    50          
844              
845             /* Detach args from tree */
846 46           OpMORESIB_set(pushop, cvop);
847 46           OpLASTSIB_set(pathop, NULL);
848 46           OpLASTSIB_set(dataop, NULL);
849              
850             /* Force scalar context on both args so function calls
851             return exactly one value on the stack */
852 46           pathop = op_contextualize(pathop, G_SCALAR);
853 46           dataop = op_contextualize(dataop, G_SCALAR);
854              
855             /* Create as OP_NULL first to avoid -DDEBUGGING assertion in newBINOP,
856             then convert to OP_CUSTOM */
857 46           newop = newBINOP(OP_NULL, 0, pathop, dataop);
858 46           newop->op_type = OP_CUSTOM;
859 46           newop->op_ppaddr = ppfunc;
860              
861 46           op_free(entersubop);
862 46           return newop;
863             }
864              
865             /* Install 1-arg function with call checker */
866 103           static void install_file_func_1arg(pTHX_ const char *pkg, const char *name,
867             XSUBADDR_t xsub, file_ppfunc ppfunc) {
868             char full_name[256];
869             CV *cv;
870             SV *ckobj;
871              
872 103           snprintf(full_name, sizeof(full_name), "%s::%s", pkg, name);
873 103           cv = newXS(full_name, xsub, __FILE__);
874              
875 103           ckobj = newSViv(PTR2IV(ppfunc));
876 103           cv_set_call_checker(cv, file_call_checker_1arg, ckobj);
877 103           }
878              
879             /* Install 2-arg function with call checker */
880 29           static void install_file_func_2arg(pTHX_ const char *pkg, const char *name,
881             XSUBADDR_t xsub, file_ppfunc ppfunc) {
882             char full_name[256];
883             CV *cv;
884             SV *ckobj;
885              
886 29           snprintf(full_name, sizeof(full_name), "%s::%s", pkg, name);
887 29           cv = newXS(full_name, xsub, __FILE__);
888              
889 29           ckobj = newSViv(PTR2IV(ppfunc));
890 29           cv_set_call_checker(cv, file_call_checker_2arg, ckobj);
891 29           }
892              
893             /* ============================================
894             Memory-mapped file registry
895             ============================================ */
896              
897             typedef struct {
898             void *addr; /* Mapped address */
899             size_t len; /* Mapped length */
900             int refcount; /* Reference count */
901             #ifdef _WIN32
902             HANDLE file_handle; /* Windows file handle */
903             HANDLE map_handle; /* Windows mapping handle */
904             #else
905             int fd; /* File descriptor (POSIX) */
906             #endif
907             } MmapEntry;
908              
909             static MmapEntry *g_mmaps = NULL;
910             static IV g_mmaps_size = 0;
911             static IV g_mmaps_count = 0;
912              
913             /* Free list for mmap reuse */
914             static IV *g_free_mmaps = NULL;
915             static IV g_free_mmaps_size = 0;
916             static IV g_free_mmaps_count = 0;
917              
918             /* ============================================
919             Line iterator registry
920             ============================================ */
921              
922             typedef struct {
923             int fd; /* File descriptor */
924             char *buffer; /* Read buffer */
925             size_t buf_size; /* Buffer size */
926             size_t buf_pos; /* Current position in buffer */
927             size_t buf_len; /* Valid data length in buffer */
928             int eof; /* End of file reached */
929             int refcount; /* Reference count */
930             char *path; /* File path (for reopening) */
931             } LineIterEntry;
932              
933             static LineIterEntry *g_iters = NULL;
934             static IV g_iters_size = 0;
935             static IV g_iters_count = 0;
936              
937             static IV *g_free_iters = NULL;
938             static IV g_free_iters_size = 0;
939             static IV g_free_iters_count = 0;
940              
941             /* ============================================
942             Initialization
943             ============================================ */
944              
945             static int file_initialized = 0;
946              
947             /* Forward declaration for callback registry init */
948             static void file_init_callback_registry(pTHX);
949              
950 18           static void file_init(pTHX) {
951 18 50         if (file_initialized) return;
952              
953 18           g_mmaps_size = 16;
954 18 50         Newxz(g_mmaps, g_mmaps_size, MmapEntry);
955 18           g_free_mmaps_size = 16;
956 18 50         Newxz(g_free_mmaps, g_free_mmaps_size, IV);
957              
958 18           g_iters_size = 16;
959 18 50         Newxz(g_iters, g_iters_size, LineIterEntry);
960 18           g_free_iters_size = 16;
961 18 50         Newxz(g_free_iters, g_free_iters_size, IV);
962              
963             /* Initialize callback registry with built-in predicates */
964 18           file_init_callback_registry(aTHX);
965              
966 18           file_initialized = 1;
967             }
968              
969             /* ============================================
970             Fast slurp - read entire file into SV
971             ============================================ */
972              
973 44           static SV* file_slurp_internal(pTHX_ const char *path) {
974             int fd;
975             Stat_t st;
976             SV *result;
977             char *buf;
978 44           ssize_t total = 0, n;
979             #ifdef _WIN32
980             int open_flags = O_RDONLY | O_BINARY;
981             #else
982             /* O_NOATIME avoids updating access time - reduces disk writes */
983             #ifdef __linux__
984 44           int open_flags = O_RDONLY | O_NOATIME;
985             #else
986             int open_flags = O_RDONLY;
987             #endif
988             #endif
989              
990 44           fd = open(path, open_flags);
991             #ifdef __linux__
992             /* Fallback if O_NOATIME fails (not owner) */
993 44 100         if (fd < 0 && errno == EPERM) {
    50          
994 0           fd = open(path, O_RDONLY);
995             }
996             #endif
997 44 100         if (fd < 0) {
998 2           return &PL_sv_undef;
999             }
1000              
1001 42 50         if (fstat(fd, &st) < 0) {
1002 0           close(fd);
1003 0           return &PL_sv_undef;
1004             }
1005              
1006             /* Hint to kernel: sequential read pattern */
1007 42           advise_sequential(fd, st.st_size);
1008              
1009             /* Pre-allocate exact size for regular files */
1010 42 50         if (S_ISREG(st.st_mode) && st.st_size > 0) {
    100          
1011             #ifndef _WIN32
1012             /* For large files, use mmap + memcpy - faster than read() syscalls */
1013 37 50         if (st.st_size >= MMAP_SLURP_THRESHOLD) {
1014 0           void *map = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
1015 0 0         if (map != MAP_FAILED) {
1016             /* Hint: we'll read sequentially */
1017             #ifdef MADV_SEQUENTIAL
1018 0           madvise(map, st.st_size, MADV_SEQUENTIAL);
1019             #endif
1020            
1021 0           result = newSV(st.st_size + 1);
1022 0           SvPOK_on(result);
1023 0           buf = SvPVX(result);
1024 0           memcpy(buf, map, st.st_size);
1025 0           buf[st.st_size] = '\0';
1026 0           SvCUR_set(result, st.st_size);
1027            
1028 0           munmap(map, st.st_size);
1029 0           close(fd);
1030 0           goto apply_hooks;
1031             }
1032             /* mmap failed, fall through to read() */
1033             }
1034             #endif
1035 37           result = newSV(st.st_size + 1);
1036 37           SvPOK_on(result);
1037 37           buf = SvPVX(result);
1038              
1039             /* Read in one shot if possible */
1040 74 100         while (total < st.st_size) {
1041 37           n = read(fd, buf + total, st.st_size - total);
1042 37 50         if (n < 0) {
1043 0 0         if (errno == EINTR) continue;
1044 0           close(fd);
1045 0           SvREFCNT_dec(result);
1046 0           return &PL_sv_undef;
1047             }
1048 37 50         if (n == 0) break;
1049 37           total += n;
1050             }
1051              
1052 37           buf[total] = '\0';
1053 37           SvCUR_set(result, total);
1054             } else {
1055             /* Stream or unknown size - read in chunks */
1056 5           size_t capacity = FILE_BUFFER_SIZE;
1057 5           result = newSV(capacity);
1058 5           SvPOK_on(result);
1059 5           buf = SvPVX(result);
1060              
1061             while (1) {
1062 5 50         if (total >= (ssize_t)capacity - 1) {
1063 0           capacity *= 2;
1064 0 0         SvGROW(result, capacity);
    0          
1065 0           buf = SvPVX(result);
1066             }
1067              
1068 5           n = read(fd, buf + total, capacity - total - 1);
1069 5 50         if (n < 0) {
1070 0 0         if (errno == EINTR) continue;
1071 0           close(fd);
1072 0           SvREFCNT_dec(result);
1073 0           return &PL_sv_undef;
1074             }
1075 5 50         if (n == 0) break;
1076 0           total += n;
1077             }
1078              
1079 5           buf[total] = '\0';
1080 5           SvCUR_set(result, total);
1081             }
1082              
1083 42           close(fd);
1084              
1085 42           apply_hooks:
1086             /* Run read hooks if registered (lazy - just pointer check) */
1087 42 100         if (g_file_read_hook || g_file_hooks[FILE_HOOK_PHASE_READ]) {
    100          
1088 9           SV *hooked = file_run_hooks(aTHX_ FILE_HOOK_PHASE_READ, path, result);
1089 9 50         if (!hooked) {
1090 0           SvREFCNT_dec(result);
1091 0           return &PL_sv_undef;
1092             }
1093 9 50         if (hooked != result) {
1094 9           SvREFCNT_dec(result);
1095 9           result = hooked;
1096             }
1097             }
1098              
1099 42           return result;
1100             }
1101              
1102             /* ============================================
1103             Fast slurp binary - same as slurp but explicit
1104             (bypasses hooks - for raw binary data)
1105             ============================================ */
1106              
1107 3           static SV* file_slurp_raw_internal(pTHX_ const char *path) {
1108             int fd;
1109             Stat_t st;
1110             SV *result;
1111             char *buf;
1112 3           ssize_t total = 0, n;
1113             #ifdef _WIN32
1114             int open_flags = O_RDONLY | O_BINARY;
1115             #else
1116             #ifdef __linux__
1117 3           int open_flags = O_RDONLY | O_NOATIME;
1118             #else
1119             int open_flags = O_RDONLY;
1120             #endif
1121             #endif
1122              
1123 3           fd = open(path, open_flags);
1124             #ifdef __linux__
1125 3 50         if (fd < 0 && errno == EPERM) {
    0          
1126 0           fd = open(path, O_RDONLY);
1127             }
1128             #endif
1129 3 50         if (fd < 0) {
1130 0           return &PL_sv_undef;
1131             }
1132              
1133 3 50         if (fstat(fd, &st) < 0) {
1134 0           close(fd);
1135 0           return &PL_sv_undef;
1136             }
1137              
1138             /* Hint to kernel: sequential read pattern */
1139 3           advise_sequential(fd, st.st_size);
1140              
1141 3 50         if (S_ISREG(st.st_mode) && st.st_size > 0) {
    50          
1142             #ifndef _WIN32
1143             /* For large files, use mmap + memcpy */
1144 3 50         if (st.st_size >= MMAP_SLURP_THRESHOLD) {
1145 0           void *map = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
1146 0 0         if (map != MAP_FAILED) {
1147             #ifdef MADV_SEQUENTIAL
1148 0           madvise(map, st.st_size, MADV_SEQUENTIAL);
1149             #endif
1150            
1151 0           result = newSV(st.st_size + 1);
1152 0           SvPOK_on(result);
1153 0           buf = SvPVX(result);
1154 0           memcpy(buf, map, st.st_size);
1155 0           buf[st.st_size] = '\0';
1156 0           SvCUR_set(result, st.st_size);
1157            
1158 0           munmap(map, st.st_size);
1159 0           close(fd);
1160 0           return result;
1161             }
1162             }
1163             #endif
1164 3           result = newSV(st.st_size + 1);
1165 3           SvPOK_on(result);
1166 3           buf = SvPVX(result);
1167              
1168 6 100         while (total < st.st_size) {
1169 3           n = read(fd, buf + total, st.st_size - total);
1170 3 50         if (n < 0) {
1171 0 0         if (errno == EINTR) continue;
1172 0           close(fd);
1173 0           SvREFCNT_dec(result);
1174 0           return &PL_sv_undef;
1175             }
1176 3 50         if (n == 0) break;
1177 3           total += n;
1178             }
1179              
1180 3           buf[total] = '\0';
1181 3           SvCUR_set(result, total);
1182             } else {
1183 0           size_t capacity = FILE_BUFFER_SIZE;
1184 0           result = newSV(capacity);
1185 0           SvPOK_on(result);
1186 0           buf = SvPVX(result);
1187              
1188             while (1) {
1189 0 0         if (total >= (ssize_t)capacity - 1) {
1190 0           capacity *= 2;
1191 0 0         SvGROW(result, capacity);
    0          
1192 0           buf = SvPVX(result);
1193             }
1194              
1195 0           n = read(fd, buf + total, capacity - total - 1);
1196 0 0         if (n < 0) {
1197 0 0         if (errno == EINTR) continue;
1198 0           close(fd);
1199 0           SvREFCNT_dec(result);
1200 0           return &PL_sv_undef;
1201             }
1202 0 0         if (n == 0) break;
1203 0           total += n;
1204             }
1205              
1206 0           buf[total] = '\0';
1207 0           SvCUR_set(result, total);
1208             }
1209              
1210 3           close(fd);
1211 3           return result; /* No hooks for raw */
1212             }
1213              
1214 2           static SV* file_slurp_raw(pTHX_ const char *path) {
1215 2           return file_slurp_raw_internal(aTHX_ path);
1216             }
1217              
1218             /* ============================================
1219             Fast spew - write SV to file
1220             ============================================ */
1221              
1222 156           static int file_spew_internal(pTHX_ const char *path, SV *data) {
1223             int fd;
1224             const char *buf;
1225             STRLEN len;
1226             ssize_t n;
1227 156           SV *write_data = data;
1228 156           int free_write_data = 0;
1229             #ifdef _WIN32
1230             int open_flags = O_WRONLY | O_CREAT | O_TRUNC | O_BINARY;
1231             #else
1232 156           int open_flags = O_WRONLY | O_CREAT | O_TRUNC;
1233             #endif
1234              
1235             /* Run write hooks if registered (lazy - just pointer check) */
1236 156 100         if (UNLIKELY(g_file_write_hook || g_file_hooks[FILE_HOOK_PHASE_WRITE])) {
    100          
1237 5           SV *hooked = file_run_hooks(aTHX_ FILE_HOOK_PHASE_WRITE, path, data);
1238 5 50         if (!hooked) {
1239 0           return 0; /* Hook cancelled the write */
1240             }
1241 5 50         if (hooked != data) {
1242 5           write_data = hooked;
1243 5           free_write_data = 1;
1244             }
1245             }
1246              
1247 156           buf = SvPV(write_data, len);
1248              
1249 156           fd = file_open3(path, open_flags, 0644);
1250 156 50         if (UNLIKELY(fd < 0)) {
1251 0 0         if (free_write_data) SvREFCNT_dec(write_data);
1252 0           return 0;
1253             }
1254              
1255             #if defined(__linux__)
1256             /* Pre-allocate space for large files to avoid fragmentation */
1257 156 100         if (len >= 65536) {
1258 4           posix_fallocate(fd, 0, len);
1259             }
1260             #endif
1261              
1262             /* Fast path: single write for common case */
1263 156           n = write(fd, buf, len);
1264 156 50         if (LIKELY(n == (ssize_t)len)) {
1265 156           close(fd);
1266 156 100         if (free_write_data) SvREFCNT_dec(write_data);
1267             /* Invalidate cache for this path */
1268 156 100         if (g_stat_cache.valid && strcmp(path, g_stat_cache.path) == 0) {
    100          
1269 2           g_stat_cache.valid = 0;
1270             }
1271 156           return 1;
1272             }
1273              
1274             /* Handle partial write or error */
1275 0 0         if (n < 0) {
1276 0 0         if (errno != EINTR) {
1277 0           close(fd);
1278 0 0         if (free_write_data) SvREFCNT_dec(write_data);
1279 0           return 0;
1280             }
1281 0           n = 0;
1282             }
1283              
1284             /* Loop for remaining data (rare) */
1285             {
1286 0           ssize_t written = n;
1287 0 0         while ((size_t)written < len) {
1288 0           n = write(fd, buf + written, len - written);
1289 0 0         if (n < 0) {
1290 0 0         if (errno == EINTR) continue;
1291 0           close(fd);
1292 0 0         if (free_write_data) SvREFCNT_dec(write_data);
1293 0           return 0;
1294             }
1295 0           written += n;
1296             }
1297             }
1298              
1299 0           close(fd);
1300 0 0         if (free_write_data) SvREFCNT_dec(write_data);
1301             /* Invalidate cache for this path */
1302 0 0         if (g_stat_cache.valid && strcmp(path, g_stat_cache.path) == 0) {
    0          
1303 0           g_stat_cache.valid = 0;
1304             }
1305 0           return 1;
1306             }
1307              
1308             /* ============================================
1309             Fast append - append SV to file
1310             ============================================ */
1311              
1312 4           static int file_append_internal(pTHX_ const char *path, SV *data) {
1313             int fd;
1314             const char *buf;
1315             STRLEN len;
1316             ssize_t n;
1317             #ifdef _WIN32
1318             int open_flags = O_WRONLY | O_CREAT | O_APPEND | O_BINARY;
1319             #else
1320 4           int open_flags = O_WRONLY | O_CREAT | O_APPEND;
1321             #endif
1322              
1323 4           buf = SvPV(data, len);
1324              
1325 4           fd = file_open3(path, open_flags, 0644);
1326 4 50         if (UNLIKELY(fd < 0)) {
1327 0           return 0;
1328             }
1329              
1330             /* Fast path: single write for common case */
1331 4           n = write(fd, buf, len);
1332 4 50         if (LIKELY(n == (ssize_t)len)) {
1333 4           close(fd);
1334             /* Invalidate cache for this path */
1335 4 50         if (g_stat_cache.valid && strcmp(path, g_stat_cache.path) == 0) {
    100          
1336 1           g_stat_cache.valid = 0;
1337             }
1338 4           return 1;
1339             }
1340              
1341             /* Handle partial write or error */
1342 0 0         if (n < 0) {
1343 0 0         if (errno != EINTR) {
1344 0           close(fd);
1345 0           return 0;
1346             }
1347 0           n = 0;
1348             }
1349              
1350             /* Loop for remaining data (rare) */
1351             {
1352 0           ssize_t written = n;
1353 0 0         while ((size_t)written < len) {
1354 0           n = write(fd, buf + written, len - written);
1355 0 0         if (n < 0) {
1356 0 0         if (errno == EINTR) continue;
1357 0           close(fd);
1358 0           return 0;
1359             }
1360 0           written += n;
1361             }
1362             }
1363              
1364 0           close(fd);
1365             /* Invalidate cache for this path */
1366 0 0         if (g_stat_cache.valid && strcmp(path, g_stat_cache.path) == 0) {
    0          
1367 0           g_stat_cache.valid = 0;
1368             }
1369 0           return 1;
1370             }
1371              
1372             /* ============================================
1373             Memory-mapped file operations
1374             ============================================ */
1375              
1376 4           static void ensure_mmaps_capacity(IV needed) {
1377 4 50         if (needed >= g_mmaps_size) {
1378 0 0         IV new_size = g_mmaps_size ? g_mmaps_size * 2 : 16;
1379             IV i;
1380 0 0         while (new_size <= needed) new_size *= 2;
1381 0 0         Renew(g_mmaps, new_size, MmapEntry);
1382 0 0         for (i = g_mmaps_size; i < new_size; i++) {
1383 0           g_mmaps[i].addr = NULL;
1384 0           g_mmaps[i].len = 0;
1385 0           g_mmaps[i].refcount = 0;
1386             #ifdef _WIN32
1387             g_mmaps[i].file_handle = INVALID_HANDLE_VALUE;
1388             g_mmaps[i].map_handle = INVALID_HANDLE_VALUE;
1389             #else
1390 0           g_mmaps[i].fd = -1;
1391             #endif
1392             }
1393 0           g_mmaps_size = new_size;
1394             }
1395 4           }
1396              
1397 14           static IV alloc_mmap_slot(void) {
1398             IV idx;
1399              
1400 14 100         if (g_free_mmaps_count > 0) {
1401 10           return g_free_mmaps[--g_free_mmaps_count];
1402             }
1403              
1404 4           ensure_mmaps_capacity(g_mmaps_count);
1405 4           idx = g_mmaps_count++;
1406 4           return idx;
1407             }
1408              
1409 14           static void free_mmap_slot(IV idx) {
1410             dTHX;
1411             MmapEntry *entry;
1412              
1413 14 50         if (idx < 0 || idx >= g_mmaps_count) return;
    50          
1414              
1415 14           entry = &g_mmaps[idx];
1416             #ifdef _WIN32
1417             if (entry->addr) {
1418             UnmapViewOfFile(entry->addr);
1419             }
1420             if (entry->map_handle != INVALID_HANDLE_VALUE) {
1421             CloseHandle(entry->map_handle);
1422             }
1423             if (entry->file_handle != INVALID_HANDLE_VALUE) {
1424             CloseHandle(entry->file_handle);
1425             }
1426             entry->file_handle = INVALID_HANDLE_VALUE;
1427             entry->map_handle = INVALID_HANDLE_VALUE;
1428             #else
1429 14 50         if (entry->addr && entry->addr != MAP_FAILED) {
    50          
1430 14           munmap(entry->addr, entry->len);
1431             }
1432 14 50         if (entry->fd >= 0) {
1433 14           close(entry->fd);
1434             }
1435 14           entry->fd = -1;
1436             #endif
1437 14           entry->addr = NULL;
1438 14           entry->len = 0;
1439 14           entry->refcount = 0;
1440              
1441 14 50         if (g_free_mmaps_count >= g_free_mmaps_size) {
1442 0           g_free_mmaps_size *= 2;
1443 0 0         Renew(g_free_mmaps, g_free_mmaps_size, IV);
1444             }
1445 14           g_free_mmaps[g_free_mmaps_count++] = idx;
1446             }
1447              
1448 16           static IV file_mmap_open(pTHX_ const char *path, int writable) {
1449             IV idx;
1450             void *addr;
1451             size_t file_size;
1452              
1453             #ifdef _WIN32
1454             HANDLE file_handle;
1455             HANDLE map_handle;
1456             LARGE_INTEGER size;
1457             DWORD access = writable ? GENERIC_READ | GENERIC_WRITE : GENERIC_READ;
1458             DWORD share = FILE_SHARE_READ;
1459             DWORD protect = writable ? PAGE_READWRITE : PAGE_READONLY;
1460             DWORD map_access = writable ? FILE_MAP_WRITE : FILE_MAP_READ;
1461              
1462             file_handle = CreateFileA(path, access, share, NULL, OPEN_EXISTING,
1463             FILE_ATTRIBUTE_NORMAL, NULL);
1464             if (file_handle == INVALID_HANDLE_VALUE) {
1465             return -1;
1466             }
1467              
1468             if (!GetFileSizeEx(file_handle, &size)) {
1469             CloseHandle(file_handle);
1470             return -1;
1471             }
1472              
1473             if (size.QuadPart == 0) {
1474             CloseHandle(file_handle);
1475             return -1;
1476             }
1477              
1478             file_size = (size_t)size.QuadPart;
1479              
1480             map_handle = CreateFileMappingA(file_handle, NULL, protect, 0, 0, NULL);
1481             if (map_handle == NULL) {
1482             CloseHandle(file_handle);
1483             return -1;
1484             }
1485              
1486             addr = MapViewOfFile(map_handle, map_access, 0, 0, 0);
1487             if (addr == NULL) {
1488             CloseHandle(map_handle);
1489             CloseHandle(file_handle);
1490             return -1;
1491             }
1492              
1493             idx = alloc_mmap_slot();
1494             g_mmaps[idx].addr = addr;
1495             g_mmaps[idx].len = file_size;
1496             g_mmaps[idx].file_handle = file_handle;
1497             g_mmaps[idx].map_handle = map_handle;
1498             g_mmaps[idx].refcount = 1;
1499              
1500             #else
1501             int fd;
1502             Stat_t st;
1503 16 100         int flags = writable ? O_RDWR : O_RDONLY;
1504 16 100         int prot = writable ? (PROT_READ | PROT_WRITE) : PROT_READ;
1505              
1506 16           fd = open(path, flags);
1507 16 100         if (fd < 0) {
1508 1           return -1;
1509             }
1510              
1511 15 50         if (fstat(fd, &st) < 0) {
1512 0           close(fd);
1513 0           return -1;
1514             }
1515              
1516 15 100         if (st.st_size == 0) {
1517             /* Can't mmap empty file */
1518 1           close(fd);
1519 1           return -1;
1520             }
1521              
1522 14           file_size = st.st_size;
1523              
1524 14           addr = mmap(NULL, st.st_size, prot, MAP_SHARED, fd, 0);
1525 14 50         if (addr == MAP_FAILED) {
1526 0           close(fd);
1527 0           return -1;
1528             }
1529              
1530 14           idx = alloc_mmap_slot();
1531 14           g_mmaps[idx].addr = addr;
1532 14           g_mmaps[idx].len = file_size;
1533 14           g_mmaps[idx].fd = fd;
1534 14           g_mmaps[idx].refcount = 1;
1535             #endif
1536              
1537 14           return idx;
1538             }
1539              
1540 14           static SV* file_mmap_get_sv(pTHX_ IV idx) {
1541             MmapEntry *entry;
1542             SV *sv;
1543              
1544 14 50         if (idx < 0 || idx >= g_mmaps_count) {
    50          
1545 0           return &PL_sv_undef;
1546             }
1547              
1548 14           entry = &g_mmaps[idx];
1549             #ifdef _WIN32
1550             if (!entry->addr) {
1551             return &PL_sv_undef;
1552             }
1553             #else
1554 14 50         if (!entry->addr || entry->addr == MAP_FAILED) {
    50          
1555 0           return &PL_sv_undef;
1556             }
1557             #endif
1558              
1559             /* Create an SV that points directly to the mapped memory */
1560 14           sv = newSV(0);
1561 14 50         SvUPGRADE(sv, SVt_PV);
1562 14           SvPV_set(sv, (char*)entry->addr);
1563 14           SvCUR_set(sv, entry->len);
1564 14           SvLEN_set(sv, 0); /* Don't free this memory! */
1565 14           SvPOK_on(sv);
1566 14           SvREADONLY_on(sv);
1567              
1568 14           return sv;
1569             }
1570              
1571 16           static void file_mmap_close(IV idx) {
1572             dTHX;
1573 16 100         if (idx < 0 || idx >= g_mmaps_count) return;
    50          
1574              
1575 14           MmapEntry *entry = &g_mmaps[idx];
1576 14           entry->refcount--;
1577 14 50         if (entry->refcount <= 0) {
1578 14           free_mmap_slot(idx);
1579             }
1580             }
1581              
1582 2           static void file_mmap_sync(IV idx) {
1583             dTHX;
1584             MmapEntry *entry;
1585              
1586 2 50         if (idx < 0 || idx >= g_mmaps_count) return;
    50          
1587              
1588 2           entry = &g_mmaps[idx];
1589             #ifdef _WIN32
1590             if (entry->addr) {
1591             FlushViewOfFile(entry->addr, entry->len);
1592             }
1593             #else
1594 2 50         if (entry->addr && entry->addr != MAP_FAILED) {
    50          
1595 2           msync(entry->addr, entry->len, MS_SYNC);
1596             }
1597             #endif
1598             }
1599              
1600             /* ============================================
1601             Line iterator operations
1602             ============================================ */
1603              
1604 7           static void ensure_iters_capacity(IV needed) {
1605 7 50         if (needed >= g_iters_size) {
1606 0 0         IV new_size = g_iters_size ? g_iters_size * 2 : 16;
1607             IV i;
1608 0 0         while (new_size <= needed) new_size *= 2;
1609 0 0         Renew(g_iters, new_size, LineIterEntry);
1610 0 0         for (i = g_iters_size; i < new_size; i++) {
1611 0           g_iters[i].fd = -1;
1612 0           g_iters[i].buffer = NULL;
1613 0           g_iters[i].buf_size = 0;
1614 0           g_iters[i].buf_pos = 0;
1615 0           g_iters[i].buf_len = 0;
1616 0           g_iters[i].eof = 0;
1617 0           g_iters[i].refcount = 0;
1618 0           g_iters[i].path = NULL;
1619             }
1620 0           g_iters_size = new_size;
1621             }
1622 7           }
1623              
1624 58           static IV alloc_iter_slot(void) {
1625             IV idx;
1626              
1627 58 100         if (g_free_iters_count > 0) {
1628 51           return g_free_iters[--g_free_iters_count];
1629             }
1630              
1631 7           ensure_iters_capacity(g_iters_count);
1632 7           idx = g_iters_count++;
1633 7           return idx;
1634             }
1635              
1636 57           static void free_iter_slot(IV idx) {
1637             dTHX;
1638             LineIterEntry *entry;
1639              
1640 57 50         if (idx < 0 || idx >= g_iters_count) return;
    50          
1641              
1642 57           entry = &g_iters[idx];
1643 57 50         if (entry->fd >= 0) {
1644 57           close(entry->fd);
1645             }
1646 57 50         if (entry->buffer) {
1647 57           Safefree(entry->buffer);
1648             }
1649 57 50         if (entry->path) {
1650 57           Safefree(entry->path);
1651             }
1652              
1653 57           entry->fd = -1;
1654 57           entry->buffer = NULL;
1655 57           entry->buf_size = 0;
1656 57           entry->buf_pos = 0;
1657 57           entry->buf_len = 0;
1658 57           entry->eof = 0;
1659 57           entry->refcount = 0;
1660 57           entry->path = NULL;
1661              
1662 57 50         if (g_free_iters_count >= g_free_iters_size) {
1663 0           g_free_iters_size *= 2;
1664 0 0         Renew(g_free_iters, g_free_iters_size, IV);
1665             }
1666 57           g_free_iters[g_free_iters_count++] = idx;
1667             }
1668              
1669 62           static IV file_lines_open(pTHX_ const char *path) {
1670             int fd;
1671             IV idx;
1672             LineIterEntry *entry;
1673             size_t path_len;
1674             #ifdef _WIN32
1675             int open_flags = O_RDONLY | O_BINARY;
1676             #else
1677 62           int open_flags = O_RDONLY;
1678             #endif
1679              
1680 62           fd = open(path, open_flags);
1681 62 100         if (fd < 0) {
1682 4           return -1;
1683             }
1684              
1685 58           idx = alloc_iter_slot();
1686 58           entry = &g_iters[idx];
1687              
1688 58           entry->fd = fd;
1689 58           entry->buf_size = FILE_BUFFER_SIZE;
1690 58           Newx(entry->buffer, entry->buf_size, char);
1691 58           entry->buf_pos = 0;
1692 58           entry->buf_len = 0;
1693 58           entry->eof = 0;
1694 58           entry->refcount = 1;
1695              
1696 58           path_len = strlen(path);
1697 58           Newx(entry->path, path_len + 1, char);
1698 58           memcpy(entry->path, path, path_len + 1);
1699              
1700 58           return idx;
1701             }
1702              
1703 1291           static SV* file_lines_next(pTHX_ IV idx) {
1704             LineIterEntry *entry;
1705             char *line_start;
1706             char *newline;
1707             size_t line_len;
1708             SV *result;
1709             ssize_t n;
1710              
1711 1291 50         if (idx < 0 || idx >= g_iters_count) {
    50          
1712 0           return &PL_sv_undef;
1713             }
1714              
1715 1291           entry = &g_iters[idx];
1716 1291 50         if (entry->fd < 0) {
1717 0           return &PL_sv_undef;
1718             }
1719              
1720             while (1) {
1721             /* Look for newline in current buffer */
1722 1352 100         if (entry->buf_pos < entry->buf_len) {
1723 1268           line_start = entry->buffer + entry->buf_pos;
1724 1268           newline = memchr(line_start, '\n', entry->buf_len - entry->buf_pos);
1725              
1726 1268 100         if (newline) {
1727 1256           line_len = newline - line_start;
1728 1256           result = newSVpvn(line_start, line_len);
1729 1256           entry->buf_pos += line_len + 1;
1730 1256           return result;
1731             }
1732             }
1733              
1734             /* No newline found, need more data */
1735 96 100         if (entry->eof) {
1736             /* Return remaining data if any */
1737 35 100         if (entry->buf_pos < entry->buf_len) {
1738 6           line_len = entry->buf_len - entry->buf_pos;
1739 6           result = newSVpvn(entry->buffer + entry->buf_pos, line_len);
1740 6           entry->buf_pos = entry->buf_len;
1741 6           return result;
1742             }
1743 29           return &PL_sv_undef;
1744             }
1745              
1746             /* Move remaining data to start of buffer */
1747 61 100         if (entry->buf_pos > 0) {
1748 26           size_t remaining = entry->buf_len - entry->buf_pos;
1749 26 100         if (remaining > 0) {
1750 6           memmove(entry->buffer, entry->buffer + entry->buf_pos, remaining);
1751             }
1752 26           entry->buf_len = remaining;
1753 26           entry->buf_pos = 0;
1754             }
1755              
1756             /* Expand buffer if needed */
1757 61 50         if (entry->buf_len >= entry->buf_size - 1) {
1758 0           entry->buf_size *= 2;
1759 0           Renew(entry->buffer, entry->buf_size, char);
1760             }
1761              
1762             /* Read more data */
1763 61           n = read(entry->fd, entry->buffer + entry->buf_len,
1764 61           entry->buf_size - entry->buf_len - 1);
1765 61 50         if (n < 0) {
1766 0 0         if (errno == EINTR) continue;
1767 0           return &PL_sv_undef;
1768             }
1769 61 100         if (n == 0) {
1770 29           entry->eof = 1;
1771             } else {
1772 32           entry->buf_len += n;
1773             }
1774             }
1775             }
1776              
1777 0           static int file_lines_eof(IV idx) {
1778             dTHX;
1779             LineIterEntry *entry;
1780              
1781 0 0         if (idx < 0 || idx >= g_iters_count) {
    0          
1782 0           return 1;
1783             }
1784              
1785 0           entry = &g_iters[idx];
1786 0 0         return entry->eof && entry->buf_pos >= entry->buf_len;
    0          
1787             }
1788              
1789 58           static void file_lines_close(IV idx) {
1790             dTHX;
1791 58 100         if (idx < 0 || idx >= g_iters_count) return;
    50          
1792              
1793 57           LineIterEntry *entry = &g_iters[idx];
1794 57           entry->refcount--;
1795 57 50         if (entry->refcount <= 0) {
1796 57           free_iter_slot(idx);
1797             }
1798             }
1799              
1800             /* ============================================
1801             Fast stat operations
1802             ============================================ */
1803              
1804 65           static IV file_size_internal(const char *path) {
1805             dTHX;
1806             Stat_t st;
1807 65 100         if (cached_stat(path, &st) < 0) {
1808 2           return -1;
1809             }
1810 63           return st.st_size;
1811             }
1812              
1813 168           static int file_exists_internal(const char *path) {
1814             dTHX;
1815             Stat_t st;
1816 168           return cached_stat(path, &st) == 0;
1817             }
1818              
1819 33           static int file_is_file_internal(const char *path) {
1820             dTHX;
1821             Stat_t st;
1822 33 100         if (cached_stat(path, &st) < 0) return 0;
1823 31           return S_ISREG(st.st_mode);
1824             }
1825              
1826 38           static int file_is_dir_internal(const char *path) {
1827             dTHX;
1828             Stat_t st;
1829 38 100         if (cached_stat(path, &st) < 0) return 0;
1830 31           return S_ISDIR(st.st_mode);
1831             }
1832              
1833 9           static int file_is_readable_internal(const char *path) {
1834             dTHX;
1835 9           return file_is_readable_cached(path);
1836             }
1837              
1838 9           static int file_is_writable_internal(const char *path) {
1839             dTHX;
1840 9           return file_is_writable_cached(path);
1841             }
1842              
1843 31           static IV file_mtime_internal(const char *path) {
1844             dTHX;
1845             Stat_t st;
1846 31 100         if (cached_stat(path, &st) < 0) {
1847 1           return -1;
1848             }
1849 30           return st.st_mtime;
1850             }
1851              
1852 8           static IV file_atime_internal(const char *path) {
1853             dTHX;
1854             Stat_t st;
1855 8 100         if (cached_stat(path, &st) < 0) {
1856 1           return -1;
1857             }
1858 7           return st.st_atime;
1859             }
1860              
1861 8           static IV file_ctime_internal(const char *path) {
1862             dTHX;
1863             Stat_t st;
1864 8 100         if (cached_stat(path, &st) < 0) {
1865 1           return -1;
1866             }
1867 7           return st.st_ctime;
1868             }
1869              
1870 5           static IV file_mode_internal(const char *path) {
1871             dTHX;
1872             Stat_t st;
1873 5 100         if (cached_stat(path, &st) < 0) {
1874 1           return -1;
1875             }
1876 4           return st.st_mode & 07777; /* Return permission bits only */
1877             }
1878              
1879             /* Combined stat - returns all attributes in one syscall */
1880 4           static HV* file_stat_all_internal(pTHX_ const char *path) {
1881             Stat_t st;
1882             HV *result;
1883              
1884 4 100         if (cached_stat(path, &st) < 0) {
1885 1           return NULL;
1886             }
1887              
1888 3           result = newHV();
1889 3           hv_store(result, "size", 4, newSViv(st.st_size), 0);
1890 3           hv_store(result, "mtime", 5, newSViv(st.st_mtime), 0);
1891 3           hv_store(result, "atime", 5, newSViv(st.st_atime), 0);
1892 3           hv_store(result, "ctime", 5, newSViv(st.st_ctime), 0);
1893 3           hv_store(result, "mode", 4, newSViv(st.st_mode & 07777), 0);
1894 3 100         hv_store(result, "is_file", 7, S_ISREG(st.st_mode) ? &PL_sv_yes : &PL_sv_no, 0);
1895 3 100         hv_store(result, "is_dir", 6, S_ISDIR(st.st_mode) ? &PL_sv_yes : &PL_sv_no, 0);
1896 3           hv_store(result, "dev", 3, newSViv(st.st_dev), 0);
1897 3           hv_store(result, "ino", 3, newSViv(st.st_ino), 0);
1898 3           hv_store(result, "nlink", 5, newSViv(st.st_nlink), 0);
1899 3           hv_store(result, "uid", 3, newSViv(st.st_uid), 0);
1900 3           hv_store(result, "gid", 3, newSViv(st.st_gid), 0);
1901              
1902 3           return result;
1903             }
1904              
1905 10           static int file_is_link_internal(const char *path) {
1906             dTHX;
1907             #ifdef _WIN32
1908             /* Windows: check for reparse point */
1909             DWORD attrs = GetFileAttributesA(path);
1910             if (attrs == INVALID_FILE_ATTRIBUTES) return 0;
1911             return (attrs & FILE_ATTRIBUTE_REPARSE_POINT) != 0;
1912             #else
1913             Stat_t st;
1914 10 100         if (lstat(path, &st) < 0) return 0;
1915 9           return S_ISLNK(st.st_mode);
1916             #endif
1917             }
1918              
1919 8           static int file_is_executable_internal(const char *path) {
1920             dTHX;
1921             #ifdef _WIN32
1922             /* Windows: check file extension */
1923             const char *ext = strrchr(path, '.');
1924             if (ext) {
1925             if (_stricmp(ext, ".exe") == 0 || _stricmp(ext, ".bat") == 0 ||
1926             _stricmp(ext, ".cmd") == 0 || _stricmp(ext, ".com") == 0) {
1927             return 1;
1928             }
1929             }
1930             return 0;
1931             #else
1932 8           return file_is_executable_cached(path);
1933             #endif
1934             }
1935              
1936             /* ============================================
1937             File manipulation operations
1938             ============================================ */
1939              
1940 6           static int file_unlink_internal(const char *path) {
1941             dTHX;
1942             int result;
1943             #ifdef _WIN32
1944             result = _unlink(path) == 0;
1945             #else
1946 6           result = unlink(path) == 0;
1947             #endif
1948             /* Invalidate cache if this path was cached */
1949 6 100         if (g_stat_cache.valid && strcmp(path, g_stat_cache.path) == 0) {
    50          
1950 2           g_stat_cache.valid = 0;
1951             }
1952 6           return result;
1953             }
1954              
1955 4           static int file_copy_internal(pTHX_ const char *src, const char *dst) {
1956             #if defined(__APPLE__)
1957             /* macOS: Use native copyfile() for best performance and metadata */
1958             Stat_t st;
1959             int result;
1960             if (stat(src, &st) < 0) return 0;
1961             result = copyfile(src, dst, NULL, COPYFILE_DATA) == 0;
1962             if (result && g_stat_cache.valid && strcmp(dst, g_stat_cache.path) == 0) {
1963             g_stat_cache.valid = 0;
1964             }
1965             return result;
1966             #elif defined(__linux__)
1967             /* Linux: Use sendfile() for zero-copy transfer */
1968             int fd_src, fd_dst;
1969             Stat_t st;
1970 4           off_t offset = 0;
1971             ssize_t sent;
1972              
1973 4           fd_src = open(src, O_RDONLY);
1974 4 100         if (fd_src < 0) return 0;
1975              
1976 3 50         if (fstat(fd_src, &st) < 0 || !S_ISREG(st.st_mode)) {
    50          
1977 0           close(fd_src);
1978 0           return 0;
1979             }
1980              
1981 3           fd_dst = file_open3(dst, O_WRONLY | O_CREAT | O_TRUNC, st.st_mode & 07777);
1982 3 50         if (fd_dst < 0) {
1983 0           close(fd_src);
1984 0           return 0;
1985             }
1986              
1987             /* sendfile() for zero-copy - much faster than read/write */
1988 5 100         while (offset < st.st_size) {
1989 2           sent = sendfile(fd_dst, fd_src, &offset, st.st_size - offset);
1990 2 50         if (sent < 0) {
1991 0 0         if (errno == EINTR) continue;
1992 0 0         if (errno == EINVAL || errno == ENOSYS) {
    0          
1993             /* sendfile not supported, fallback to read/write */
1994             char *buffer;
1995             ssize_t n_read, n_written, written;
1996 0           int result = 0;
1997              
1998             /* Reposition to where we left off */
1999 0           lseek(fd_src, offset, SEEK_SET);
2000              
2001 0           Newx(buffer, FILE_BULK_BUFFER_SIZE, char);
2002             while (1) {
2003 0           n_read = read(fd_src, buffer, FILE_BULK_BUFFER_SIZE);
2004 0 0         if (n_read < 0) {
2005 0 0         if (errno == EINTR) continue;
2006 0           break;
2007             }
2008 0 0         if (n_read == 0) { result = 1; break; }
2009              
2010 0           written = 0;
2011 0 0         while (written < n_read) {
2012 0           n_written = write(fd_dst, buffer + written, n_read - written);
2013 0 0         if (n_written < 0) {
2014 0 0         if (errno == EINTR) continue;
2015 0           goto fallback_cleanup;
2016             }
2017 0           written += n_written;
2018             }
2019             }
2020 0           fallback_cleanup:
2021 0           Safefree(buffer);
2022 0           close(fd_src);
2023 0           close(fd_dst);
2024 0 0         if (result && g_stat_cache.valid && strcmp(dst, g_stat_cache.path) == 0) {
    0          
    0          
2025 0           g_stat_cache.valid = 0;
2026             }
2027 0           return result;
2028             }
2029 0           close(fd_src);
2030 0           close(fd_dst);
2031 0           return 0;
2032             }
2033 2 50         if (sent == 0) break;
2034             }
2035              
2036 3           close(fd_src);
2037 3           close(fd_dst);
2038             /* Invalidate cache for dst */
2039 3 100         if (g_stat_cache.valid && strcmp(dst, g_stat_cache.path) == 0) {
    50          
2040 0           g_stat_cache.valid = 0;
2041             }
2042 3           return 1;
2043             #else
2044             /* Portable fallback: read/write loop */
2045             int fd_src, fd_dst;
2046             char *buffer;
2047             ssize_t n_read, n_written, written;
2048             Stat_t st;
2049             int result = 0;
2050             #ifdef _WIN32
2051             int open_flags_r = O_RDONLY | O_BINARY;
2052             int open_flags_w = O_WRONLY | O_CREAT | O_TRUNC | O_BINARY;
2053             #else
2054             int open_flags_r = O_RDONLY;
2055             int open_flags_w = O_WRONLY | O_CREAT | O_TRUNC;
2056             #endif
2057              
2058             fd_src = open(src, open_flags_r);
2059             if (fd_src < 0) return 0;
2060              
2061             if (fstat(fd_src, &st) < 0) {
2062             close(fd_src);
2063             return 0;
2064             }
2065              
2066             fd_dst = file_open3(dst, open_flags_w, st.st_mode & 07777);
2067             if (fd_dst < 0) {
2068             close(fd_src);
2069             return 0;
2070             }
2071              
2072             Newx(buffer, FILE_BULK_BUFFER_SIZE, char);
2073              
2074             while (1) {
2075             n_read = read(fd_src, buffer, FILE_BULK_BUFFER_SIZE);
2076             if (n_read < 0) {
2077             if (errno == EINTR) continue;
2078             goto cleanup;
2079             }
2080             if (n_read == 0) break;
2081              
2082             written = 0;
2083             while (written < n_read) {
2084             n_written = write(fd_dst, buffer + written, n_read - written);
2085             if (n_written < 0) {
2086             if (errno == EINTR) continue;
2087             goto cleanup;
2088             }
2089             written += n_written;
2090             }
2091             }
2092              
2093             result = 1;
2094              
2095             cleanup:
2096             Safefree(buffer);
2097             close(fd_src);
2098             close(fd_dst);
2099             if (result && g_stat_cache.valid && strcmp(dst, g_stat_cache.path) == 0) {
2100             g_stat_cache.valid = 0;
2101             }
2102             return result;
2103             #endif
2104             }
2105              
2106 3           static int file_move_internal(pTHX_ const char *src, const char *dst) {
2107             int result;
2108            
2109             /* Try rename first (fast path for same filesystem) */
2110 3 100         if (rename(src, dst) == 0) {
2111 2           result = 1;
2112             }
2113             /* If EXDEV, copy then delete (cross-device move) */
2114 1 50         else if (errno == EXDEV) {
2115 0 0         if (file_copy_internal(aTHX_ src, dst)) {
2116 0           result = file_unlink_internal(src);
2117             } else {
2118 0           return 0;
2119             }
2120             } else {
2121 1           return 0;
2122             }
2123            
2124             /* Invalidate cache for both paths */
2125 2 100         if (g_stat_cache.valid) {
2126 1 50         if (strcmp(src, g_stat_cache.path) == 0 || strcmp(dst, g_stat_cache.path) == 0) {
    50          
2127 0           g_stat_cache.valid = 0;
2128             }
2129             }
2130 2           return result;
2131             }
2132              
2133 3           static int file_touch_internal(const char *path) {
2134             dTHX;
2135             int result;
2136             #ifdef _WIN32
2137             HANDLE h;
2138             FILETIME ft;
2139             SYSTEMTIME st;
2140             result = 0;
2141              
2142             /* Try to open existing file */
2143             h = CreateFileA(path, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,
2144             NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
2145             if (h == INVALID_HANDLE_VALUE) {
2146             return 0;
2147             }
2148              
2149             GetSystemTime(&st);
2150             SystemTimeToFileTime(&st, &ft);
2151             result = SetFileTime(h, NULL, &ft, &ft) != 0;
2152             CloseHandle(h);
2153             #else
2154             int fd;
2155             /* Try to update times on existing file - utime(path, NULL) sets to current time */
2156 3 100         if (utime(path, NULL) == 0) {
2157 1           result = 1;
2158             } else {
2159             /* File doesn't exist, create it */
2160 2           fd = file_open3(path, O_WRONLY | O_CREAT, 0644);
2161 2 50         if (fd < 0) {
2162 0           return 0;
2163             }
2164 2           close(fd);
2165 2           result = 1;
2166             }
2167             #endif
2168             /* Invalidate cache if this path was cached */
2169 3 100         if (g_stat_cache.valid && strcmp(path, g_stat_cache.path) == 0) {
    100          
2170 1           g_stat_cache.valid = 0;
2171             }
2172 3           return result;
2173             }
2174              
2175 1           static int file_chmod_internal(const char *path, int mode) {
2176             dTHX;
2177             int result;
2178             #ifdef _WIN32
2179             result = _chmod(path, mode) == 0;
2180             #else
2181 1           result = chmod(path, mode) == 0;
2182             #endif
2183             /* Invalidate cache if this path was cached */
2184 1 50         if (g_stat_cache.valid && strcmp(path, g_stat_cache.path) == 0) {
    50          
2185 0           g_stat_cache.valid = 0;
2186             }
2187 1           return result;
2188             }
2189              
2190 18           static int file_mkdir_internal(const char *path, int mode) {
2191             dTHX;
2192             int result;
2193             #ifdef _WIN32
2194             PERL_UNUSED_VAR(mode);
2195             result = _mkdir(path) == 0;
2196             #else
2197 18           result = mkdir(path, mode) == 0;
2198             #endif
2199             /* Invalidate cache if this path was cached */
2200 18 100         if (g_stat_cache.valid && strcmp(path, g_stat_cache.path) == 0) {
    100          
2201 1           g_stat_cache.valid = 0;
2202             }
2203 18           return result;
2204             }
2205              
2206 9           static int file_rmdir_internal(const char *path) {
2207             dTHX;
2208             int result;
2209             #ifdef _WIN32
2210             result = _rmdir(path) == 0;
2211             #else
2212 9           result = rmdir(path) == 0;
2213             #endif
2214             /* Invalidate cache if this path was cached */
2215 9 100         if (g_stat_cache.valid && strcmp(path, g_stat_cache.path) == 0) {
    50          
2216 2           g_stat_cache.valid = 0;
2217             }
2218 9           return result;
2219             }
2220              
2221             /* ============================================
2222             Directory listing
2223             ============================================ */
2224              
2225 7           static AV* file_readdir_internal(pTHX_ const char *path) {
2226 7           AV *result = newAV();
2227              
2228             #ifdef _WIN32
2229             WIN32_FIND_DATAA fd;
2230             HANDLE h;
2231             char pattern[MAX_PATH];
2232             size_t len = strlen(path);
2233              
2234             if (len + 3 > MAX_PATH) return result;
2235              
2236             memcpy(pattern, path, len);
2237             if (len > 0 && path[len-1] != '\\' && path[len-1] != '/') {
2238             pattern[len++] = '\\';
2239             }
2240             pattern[len++] = '*';
2241             pattern[len] = '\0';
2242              
2243             h = FindFirstFileA(pattern, &fd);
2244             if (h == INVALID_HANDLE_VALUE) return result;
2245              
2246             do {
2247             /* Skip . and .. */
2248             if (strcmp(fd.cFileName, ".") != 0 && strcmp(fd.cFileName, "..") != 0) {
2249             av_push(result, newSVpv(fd.cFileName, 0));
2250             }
2251             } while (FindNextFileA(h, &fd));
2252              
2253             FindClose(h);
2254             #else
2255             DIR *dir;
2256             struct dirent *entry;
2257              
2258 7           dir = opendir(path);
2259 7 100         if (!dir) return result;
2260              
2261 32 100         while ((entry = readdir(dir)) != NULL) {
2262             /* Skip . and .. */
2263 27 100         if (strcmp(entry->d_name, ".") != 0 && strcmp(entry->d_name, "..") != 0) {
    100          
2264 17           av_push(result, newSVpv(entry->d_name, 0));
2265             }
2266             }
2267              
2268 5           closedir(dir);
2269             #endif
2270              
2271 5           return result;
2272             }
2273              
2274             /* ============================================
2275             Path manipulation
2276             ============================================ */
2277              
2278 83           static SV* file_basename_internal(pTHX_ const char *path) {
2279             const char *p;
2280 83           size_t len = strlen(path);
2281              
2282 83 100         if (len == 0) return newSVpvs("");
2283              
2284             /* Skip trailing slashes */
2285 88 100         while (len > 0 && (path[len-1] == '/' || path[len-1] == '\\')) {
    100          
    50          
2286 7           len--;
2287             }
2288 81 100         if (len == 0) return newSVpvs("");
2289              
2290             /* Find last separator */
2291 78           p = path + len - 1;
2292 735 100         while (p > path && *p != '/' && *p != '\\') {
    100          
    50          
2293 657           p--;
2294             }
2295 78 100         if (*p == '/' || *p == '\\') p++;
    50          
2296              
2297 78           return newSVpvn(p, (path + len) - p);
2298             }
2299              
2300 20           static SV* file_dirname_internal(pTHX_ const char *path) {
2301             const char *end;
2302 20           size_t len = strlen(path);
2303              
2304 20 100         if (len == 0) return newSVpvs(".");
2305              
2306             /* Skip trailing slashes */
2307 19           end = path + len - 1;
2308 21 100         while (end > path && (*end == '/' || *end == '\\')) {
    100          
    50          
2309 2           end--;
2310             }
2311              
2312             /* Find last separator */
2313 137 100         while (end > path && *end != '/' && *end != '\\') {
    100          
    50          
2314 118           end--;
2315             }
2316              
2317 19 100         if (end == path) {
2318 6 100         if (*end == '/' || *end == '\\') {
    50          
2319 4           return newSVpvn(path, 1);
2320             }
2321 2           return newSVpvs(".");
2322             }
2323              
2324             /* Skip multiple trailing slashes in dirname */
2325 14 50         while (end > path && (*(end-1) == '/' || *(end-1) == '\\')) {
    100          
    50          
2326 1           end--;
2327             }
2328              
2329 13           return newSVpvn(path, end - path);
2330             }
2331              
2332 49           static SV* file_extname_internal(pTHX_ const char *path) {
2333             const char *dot;
2334             const char *basename;
2335 49           size_t len = strlen(path);
2336              
2337 49 100         if (len == 0) return newSVpvs("");
2338              
2339             /* Find basename first */
2340 48           basename = path + len - 1;
2341 452 100         while (basename > path && *basename != '/' && *basename != '\\') {
    100          
    50          
2342 404           basename--;
2343             }
2344 48 100         if (*basename == '/' || *basename == '\\') basename++;
    50          
2345              
2346             /* Find last dot in basename */
2347 48           dot = strrchr(basename, '.');
2348 48 100         if (!dot || dot == basename) return newSVpvs("");
    100          
2349              
2350 42           return newSVpv(dot, 0);
2351             }
2352              
2353 12           static SV* file_join_internal(pTHX_ AV *parts) {
2354             SV *result;
2355             SSize_t i, len;
2356 12           STRLEN total_len = 0;
2357             char *buf, *p;
2358             int need_sep;
2359              
2360 12           len = av_len(parts) + 1;
2361 12 50         if (len == 0) return newSVpvs("");
2362              
2363             /* Calculate total length */
2364 40 100         for (i = 0; i < len; i++) {
2365 28           SV **sv = av_fetch(parts, i, 0);
2366 28 50         if (sv && SvPOK(*sv)) {
    50          
2367 28           total_len += SvCUR(*sv) + 1; /* +1 for separator */
2368             }
2369             }
2370              
2371 12           result = newSV(total_len + 1);
2372 12           SvPOK_on(result);
2373 12           buf = SvPVX(result);
2374 12           p = buf;
2375 12           need_sep = 0;
2376              
2377 40 100         for (i = 0; i < len; i++) {
2378 28           SV **sv = av_fetch(parts, i, 0);
2379 28 50         if (sv && SvPOK(*sv)) {
    50          
2380             STRLEN part_len;
2381 28           const char *part = SvPV(*sv, part_len);
2382              
2383 28 100         if (part_len == 0) continue;
2384              
2385             /* Skip leading separator if we already have one */
2386 29 50         while (part_len > 0 && (*part == '/' || *part == '\\')) {
    100          
    50          
2387 9 50         if (!need_sep && p == buf) break; /* Keep root slash */
    100          
2388 2           part++;
2389 2           part_len--;
2390             }
2391              
2392 27 100         if (need_sep && part_len > 0) {
    50          
2393             #ifdef _WIN32
2394             *p++ = '\\';
2395             #else
2396 12           *p++ = '/';
2397             #endif
2398             }
2399              
2400 27 50         if (part_len > 0) {
2401 27           memcpy(p, part, part_len);
2402 27           p += part_len;
2403              
2404             /* Check if ends with separator */
2405 27 100         need_sep = (*(p-1) != '/' && *(p-1) != '\\');
    50          
2406             }
2407             }
2408             }
2409              
2410 12           *p = '\0';
2411 12           SvCUR_set(result, p - buf);
2412 12           return result;
2413             }
2414              
2415             /* ============================================
2416             Head and Tail operations
2417             ============================================ */
2418              
2419 6           static AV* file_head_internal(pTHX_ const char *path, IV n) {
2420 6           AV *result = newAV();
2421             IV idx;
2422             SV *line;
2423 6           IV count = 0;
2424              
2425 6 100         if (n <= 0) return result;
2426              
2427 5           idx = file_lines_open(aTHX_ path);
2428 5 100         if (idx < 0) return result;
2429              
2430 22 100         while (count < n && (line = file_lines_next(aTHX_ idx)) != &PL_sv_undef) {
    100          
2431 18           av_push(result, line);
2432 18           count++;
2433             }
2434              
2435 4           file_lines_close(idx);
2436 4           return result;
2437             }
2438              
2439 6           static AV* file_tail_internal(pTHX_ const char *path, IV n) {
2440 6           AV *result = newAV();
2441             AV *buffer;
2442             SV *line;
2443             IV idx;
2444             SSize_t i, buf_len;
2445              
2446 6 100         if (n <= 0) return result;
2447              
2448 5           idx = file_lines_open(aTHX_ path);
2449 5 100         if (idx < 0) return result;
2450              
2451             /* Use circular buffer to keep last N lines */
2452 4           buffer = newAV();
2453 4           av_extend(buffer, n - 1);
2454              
2455 47 100         while ((line = file_lines_next(aTHX_ idx)) != &PL_sv_undef) {
2456 43 100         if (av_len(buffer) + 1 >= n) {
2457 25           SV *old = av_shift(buffer);
2458 25           SvREFCNT_dec(old);
2459             }
2460 43           av_push(buffer, line);
2461             }
2462              
2463 4           file_lines_close(idx);
2464              
2465             /* Copy buffer to result */
2466 4           buf_len = av_len(buffer) + 1;
2467 22 100         for (i = 0; i < buf_len; i++) {
2468 18           SV **sv = av_fetch(buffer, i, 0);
2469 18 50         if (sv) {
2470 18           av_push(result, newSVsv(*sv));
2471             }
2472             }
2473              
2474 4           SvREFCNT_dec((SV*)buffer);
2475 4           return result;
2476             }
2477              
2478             /* ============================================
2479             Atomic spew - write to temp file then rename
2480             ============================================ */
2481              
2482 14           static int file_atomic_spew_internal(pTHX_ const char *path, SV *data) {
2483             char temp_path[4096];
2484             int fd;
2485             const char *buf;
2486             STRLEN len;
2487 14           ssize_t written = 0, n;
2488             static int counter = 0;
2489             #ifdef _WIN32
2490             int open_flags = O_WRONLY | O_CREAT | O_TRUNC | O_BINARY;
2491             int pid = (int)GetCurrentProcessId();
2492             #else
2493 14           int open_flags = O_WRONLY | O_CREAT | O_TRUNC;
2494 14           int pid = (int)getpid();
2495             #endif
2496              
2497             /* Create temp file name in same directory */
2498 14           snprintf(temp_path, sizeof(temp_path), "%s.tmp.%d.%d", path, pid, counter++);
2499              
2500 14           buf = SvPV(data, len);
2501              
2502 14           fd = file_open3(temp_path, open_flags, 0644);
2503 14 50         if (fd < 0) {
2504 0           return 0;
2505             }
2506              
2507 27 100         while ((size_t)written < len) {
2508 13           n = write(fd, buf + written, len - written);
2509 13 50         if (n < 0) {
2510 0 0         if (errno == EINTR) continue;
2511 0           close(fd);
2512 0           file_unlink_internal(temp_path);
2513 0           return 0;
2514             }
2515 13           written += n;
2516             }
2517              
2518             #ifdef _WIN32
2519             /* Sync to disk on Windows */
2520             _commit(fd);
2521             #else
2522             /* Sync to disk on POSIX */
2523 14           fsync(fd);
2524             #endif
2525              
2526 14           close(fd);
2527              
2528             /* Atomic rename */
2529 14 50         if (rename(temp_path, path) != 0) {
2530 0           file_unlink_internal(temp_path);
2531 0           return 0;
2532             }
2533              
2534             /* Invalidate cache for this path */
2535 14 100         if (g_stat_cache.valid && strcmp(path, g_stat_cache.path) == 0) {
    50          
2536 0           g_stat_cache.valid = 0;
2537             }
2538 14           return 1;
2539             }
2540              
2541             /* ============================================
2542             Split lines utility
2543             ============================================ */
2544              
2545 6           static AV* file_split_lines(pTHX_ SV *content) {
2546             AV *lines;
2547             const char *start, *end, *p;
2548             STRLEN len;
2549              
2550 6           start = SvPV(content, len);
2551 6           end = start + len;
2552 6           lines = newAV();
2553              
2554 16 100         while (start < end) {
2555 14           p = memchr(start, '\n', end - start);
2556 14 100         if (p) {
2557 10           av_push(lines, newSVpvn(start, p - start));
2558 10           start = p + 1;
2559             } else {
2560 4 50         if (start < end) {
2561 4           av_push(lines, newSVpvn(start, end - start));
2562             }
2563 4           break;
2564             }
2565             }
2566              
2567 6           return lines;
2568             }
2569              
2570             /* ============================================
2571             XS Functions
2572             ============================================ */
2573              
2574 29           XS_INTERNAL(xs_slurp) {
2575 29           dXSARGS;
2576             const char *path;
2577              
2578 29 50         if (items != 1) croak("Usage: file::slurp(path)");
2579              
2580 29           path = SvPV_nolen(ST(0));
2581 29           ST(0) = sv_2mortal(file_slurp_internal(aTHX_ path));
2582 29           XSRETURN(1);
2583             }
2584              
2585 2           XS_INTERNAL(xs_slurp_raw) {
2586 2           dXSARGS;
2587             const char *path;
2588              
2589 2 50         if (items != 1) croak("Usage: file::slurp_raw(path)");
2590              
2591 2           path = SvPV_nolen(ST(0));
2592 2           ST(0) = sv_2mortal(file_slurp_raw(aTHX_ path));
2593 2           XSRETURN(1);
2594             }
2595              
2596 85           XS_INTERNAL(xs_spew) {
2597 85           dXSARGS;
2598             const char *path;
2599              
2600 85 50         if (items != 2) croak("Usage: file::spew(path, data)");
2601              
2602 85           path = SvPV_nolen(ST(0));
2603 85 50         if (file_spew_internal(aTHX_ path, ST(1))) {
2604 85           ST(0) = &PL_sv_yes;
2605             } else {
2606 0           ST(0) = &PL_sv_no;
2607             }
2608 85           XSRETURN(1);
2609             }
2610              
2611 2           XS_INTERNAL(xs_append) {
2612 2           dXSARGS;
2613             const char *path;
2614              
2615 2 50         if (items != 2) croak("Usage: file::append(path, data)");
2616              
2617 2           path = SvPV_nolen(ST(0));
2618 2 50         if (file_append_internal(aTHX_ path, ST(1))) {
2619 2           ST(0) = &PL_sv_yes;
2620             } else {
2621 0           ST(0) = &PL_sv_no;
2622             }
2623 2           XSRETURN(1);
2624             }
2625              
2626 11           XS_INTERNAL(xs_size) {
2627 11           dXSARGS;
2628             const char *path;
2629             IV size;
2630              
2631 11 50         if (items != 1) croak("Usage: file::size(path)");
2632              
2633 11           path = SvPV_nolen(ST(0));
2634 11           size = file_size_internal(path);
2635 11           ST(0) = sv_2mortal(newSViv(size));
2636 11           XSRETURN(1);
2637             }
2638              
2639 4           XS_INTERNAL(xs_mtime) {
2640 4           dXSARGS;
2641             const char *path;
2642             IV mtime;
2643              
2644 4 50         if (items != 1) croak("Usage: file::mtime(path)");
2645              
2646 4           path = SvPV_nolen(ST(0));
2647 4           mtime = file_mtime_internal(path);
2648 4           ST(0) = sv_2mortal(newSViv(mtime));
2649 4           XSRETURN(1);
2650             }
2651              
2652 21           XS_INTERNAL(xs_exists) {
2653 21           dXSARGS;
2654             const char *path;
2655              
2656 21 50         if (items != 1) croak("Usage: file::exists(path)");
2657              
2658 21           path = SvPV_nolen(ST(0));
2659 21 100         ST(0) = file_exists_internal(path) ? &PL_sv_yes : &PL_sv_no;
2660 21           XSRETURN(1);
2661             }
2662              
2663 4           XS_INTERNAL(xs_is_file) {
2664 4           dXSARGS;
2665             const char *path;
2666              
2667 4 50         if (items != 1) croak("Usage: file::is_file(path)");
2668              
2669 4           path = SvPV_nolen(ST(0));
2670 4 100         ST(0) = file_is_file_internal(path) ? &PL_sv_yes : &PL_sv_no;
2671 4           XSRETURN(1);
2672             }
2673              
2674 7           XS_INTERNAL(xs_is_dir) {
2675 7           dXSARGS;
2676             const char *path;
2677              
2678 7 50         if (items != 1) croak("Usage: file::is_dir(path)");
2679              
2680 7           path = SvPV_nolen(ST(0));
2681 7 100         ST(0) = file_is_dir_internal(path) ? &PL_sv_yes : &PL_sv_no;
2682 7           XSRETURN(1);
2683             }
2684              
2685 2           XS_INTERNAL(xs_is_readable) {
2686 2           dXSARGS;
2687             const char *path;
2688              
2689 2 50         if (items != 1) croak("Usage: file::is_readable(path)");
2690              
2691 2           path = SvPV_nolen(ST(0));
2692 2 100         ST(0) = file_is_readable_internal(path) ? &PL_sv_yes : &PL_sv_no;
2693 2           XSRETURN(1);
2694             }
2695              
2696 3           XS_INTERNAL(xs_is_writable) {
2697 3           dXSARGS;
2698             const char *path;
2699              
2700 3 50         if (items != 1) croak("Usage: file::is_writable(path)");
2701              
2702 3           path = SvPV_nolen(ST(0));
2703 3 100         ST(0) = file_is_writable_internal(path) ? &PL_sv_yes : &PL_sv_no;
2704 3           XSRETURN(1);
2705             }
2706              
2707 7           XS_INTERNAL(xs_lines) {
2708 7           dXSARGS;
2709             const char *path;
2710             AV *lines;
2711             int fd;
2712             Stat_t st;
2713             char *buffer;
2714             char *p, *end, *line_start;
2715             size_t file_size;
2716             ssize_t total_read, n;
2717             #ifdef _WIN32
2718             int open_flags = O_RDONLY | O_BINARY;
2719             #else
2720 7           int open_flags = O_RDONLY;
2721             #endif
2722              
2723 7 50         if (items != 1) croak("Usage: file::lines(path)");
2724              
2725 7           path = SvPV_nolen(ST(0));
2726              
2727 7           fd = open(path, open_flags);
2728 7 100         if (UNLIKELY(fd < 0)) {
2729 1           ST(0) = sv_2mortal(newRV_noinc((SV*)newAV()));
2730 1           XSRETURN(1);
2731             }
2732              
2733             /* Get file size for single-read optimization */
2734 6 50         if (UNLIKELY(fstat(fd, &st) < 0 || st.st_size == 0)) {
    100          
2735 1           close(fd);
2736 1           ST(0) = sv_2mortal(newRV_noinc((SV*)newAV()));
2737 1           XSRETURN(1);
2738             }
2739              
2740 5           file_size = st.st_size;
2741 5           Newx(buffer, file_size + 1, char);
2742              
2743             /* Read entire file in one syscall when possible */
2744 5           total_read = 0;
2745 10 100         while ((size_t)total_read < file_size) {
2746 5           n = read(fd, buffer + total_read, file_size - total_read);
2747 5 50         if (UNLIKELY(n < 0)) {
2748 0 0         if (errno == EINTR) continue;
2749 0           break;
2750             }
2751 5 50         if (n == 0) break;
2752 5           total_read += n;
2753             }
2754 5           close(fd);
2755              
2756 5 50         if (UNLIKELY(total_read == 0)) {
2757 0           Safefree(buffer);
2758 0           ST(0) = sv_2mortal(newRV_noinc((SV*)newAV()));
2759 0           XSRETURN(1);
2760             }
2761              
2762 5           lines = newAV();
2763             /* Pre-extend array based on estimated lines */
2764 5           av_extend(lines, total_read / 40);
2765              
2766             /* Single scan through buffer - no memmove, no buffer resize */
2767 5           line_start = buffer;
2768 5           end = buffer + total_read;
2769 5           p = buffer;
2770              
2771 15 50         while (p < end) {
2772 15           p = memchr(p, '\n', end - p);
2773 15 100         if (LIKELY(p != NULL)) {
2774 10           av_push(lines, newSVpvn(line_start, p - line_start));
2775 10           p++;
2776 10           line_start = p;
2777             } else {
2778             /* Last line without trailing newline */
2779 5 50         if (line_start < end) {
2780 5           av_push(lines, newSVpvn(line_start, end - line_start));
2781             }
2782 5           break;
2783             }
2784             }
2785              
2786 5           Safefree(buffer);
2787              
2788 5           ST(0) = sv_2mortal(newRV_noinc((SV*)lines));
2789 5           XSRETURN(1);
2790             }
2791              
2792 16           XS_INTERNAL(xs_mmap_open) {
2793 16           dXSARGS;
2794             const char *path;
2795             int writable;
2796             IV idx;
2797             HV *hash;
2798              
2799 16 50         if (items < 1 || items > 2) croak("Usage: file::mmap_open(path, [writable])");
    50          
2800              
2801 16           path = SvPV_nolen(ST(0));
2802 16 100         writable = (items > 1 && SvTRUE(ST(1))) ? 1 : 0;
    50          
2803              
2804 16           idx = file_mmap_open(aTHX_ path, writable);
2805 16 100         if (idx < 0) {
2806 2           ST(0) = &PL_sv_undef;
2807 2           XSRETURN(1);
2808             }
2809              
2810 14           hash = newHV();
2811 14           hv_store(hash, "_idx", 4, newSViv(idx), 0);
2812 14           hv_store(hash, "_writable", 9, newSViv(writable), 0);
2813              
2814 14           ST(0) = sv_2mortal(sv_bless(newRV_noinc((SV*)hash), gv_stashpv("File::Raw::mmap", GV_ADD)));
2815 14           XSRETURN(1);
2816             }
2817              
2818 14           XS_INTERNAL(xs_mmap_data) {
2819 14           dXSARGS;
2820             HV *hash;
2821             SV **idx_sv;
2822             IV idx;
2823              
2824 14 50         if (items != 1) croak("Usage: $mmap->data");
2825              
2826 14 50         if (!SvROK(ST(0)) || SvTYPE(SvRV(ST(0))) != SVt_PVHV) {
    50          
2827 0           croak("Invalid mmap object");
2828             }
2829              
2830 14           hash = (HV*)SvRV(ST(0));
2831 14           idx_sv = hv_fetch(hash, "_idx", 4, 0);
2832 14 50         idx = idx_sv ? SvIV(*idx_sv) : -1;
2833              
2834 14           ST(0) = sv_2mortal(file_mmap_get_sv(aTHX_ idx));
2835 14           XSRETURN(1);
2836             }
2837              
2838 2           XS_INTERNAL(xs_mmap_sync) {
2839 2           dXSARGS;
2840             HV *hash;
2841             SV **idx_sv;
2842             IV idx;
2843              
2844 2 50         if (items != 1) croak("Usage: $mmap->sync");
2845              
2846 2 50         if (!SvROK(ST(0)) || SvTYPE(SvRV(ST(0))) != SVt_PVHV) {
    50          
2847 0           croak("Invalid mmap object");
2848             }
2849              
2850 2           hash = (HV*)SvRV(ST(0));
2851 2           idx_sv = hv_fetch(hash, "_idx", 4, 0);
2852 2 50         idx = idx_sv ? SvIV(*idx_sv) : -1;
2853              
2854 2           file_mmap_sync(idx);
2855 2           XSRETURN_EMPTY;
2856             }
2857              
2858 16           XS_INTERNAL(xs_mmap_close) {
2859 16           dXSARGS;
2860             HV *hash;
2861             SV **idx_sv;
2862             IV idx;
2863              
2864 16 50         if (items != 1) croak("Usage: $mmap->close");
2865              
2866 16 50         if (!SvROK(ST(0)) || SvTYPE(SvRV(ST(0))) != SVt_PVHV) {
    50          
2867 0           croak("Invalid mmap object");
2868             }
2869              
2870 16           hash = (HV*)SvRV(ST(0));
2871 16           idx_sv = hv_fetch(hash, "_idx", 4, 0);
2872 16 50         idx = idx_sv ? SvIV(*idx_sv) : -1;
2873              
2874 16           file_mmap_close(idx);
2875 16           hv_store(hash, "_idx", 4, newSViv(-1), 0);
2876 16           XSRETURN_EMPTY;
2877             }
2878              
2879 14           XS_INTERNAL(xs_mmap_DESTROY) {
2880 14           dXSARGS;
2881             HV *hash;
2882             SV **idx_sv;
2883             IV idx;
2884              
2885             PERL_UNUSED_VAR(items);
2886              
2887 14 50         if (PL_dirty) XSRETURN_EMPTY;
2888              
2889 14 50         if (!SvROK(ST(0)) || SvTYPE(SvRV(ST(0))) != SVt_PVHV) {
    50          
2890 0           XSRETURN_EMPTY;
2891             }
2892              
2893 14           hash = (HV*)SvRV(ST(0));
2894 14           idx_sv = hv_fetch(hash, "_idx", 4, 0);
2895 14 50         idx = idx_sv ? SvIV(*idx_sv) : -1;
2896              
2897 14 50         if (idx >= 0) {
2898 0           file_mmap_close(idx);
2899             }
2900 14           XSRETURN_EMPTY;
2901             }
2902              
2903 18           XS_INTERNAL(xs_lines_iter) {
2904 18           dXSARGS;
2905             const char *path;
2906             IV idx;
2907             SV *idx_sv;
2908              
2909 18 50         if (items != 1) croak("Usage: file::lines_iter(path)");
2910              
2911 18           path = SvPV_nolen(ST(0));
2912 18           idx = file_lines_open(aTHX_ path);
2913              
2914 18 100         if (idx < 0) {
2915 1           ST(0) = &PL_sv_undef;
2916 1           XSRETURN(1);
2917             }
2918              
2919             /* Use simple IV reference - much faster than hash */
2920 17           idx_sv = newSViv(idx);
2921 17           ST(0) = sv_2mortal(sv_bless(newRV_noinc(idx_sv), gv_stashpv("File::Raw::lines", GV_ADD)));
2922 17           XSRETURN(1);
2923             }
2924              
2925 153           XS_INTERNAL(xs_lines_iter_next) {
2926 153           dXSARGS;
2927             SV *rv;
2928             IV idx;
2929             LineIterEntry *entry;
2930             char *line_start;
2931             char *newline;
2932             size_t line_len;
2933             SV *result;
2934             ssize_t n;
2935              
2936 153 50         if (items != 1) croak("Usage: $iter->next");
2937              
2938 153           rv = ST(0);
2939 153 50         if (UNLIKELY(!SvROK(rv))) {
2940 0           croak("Invalid lines iterator object");
2941             }
2942              
2943             /* Direct IV access - no hash lookup */
2944 153           idx = SvIV(SvRV(rv));
2945              
2946 153 50         if (UNLIKELY(idx < 0 || idx >= g_iters_count)) {
    50          
2947 0           ST(0) = &PL_sv_undef;
2948 0           XSRETURN(1);
2949             }
2950              
2951 153           entry = &g_iters[idx];
2952 153 50         if (UNLIKELY(entry->fd < 0)) {
2953 0           ST(0) = &PL_sv_undef;
2954 0           XSRETURN(1);
2955             }
2956              
2957             /* Inline buffer parsing for speed */
2958             while (1) {
2959             /* Look for newline in current buffer */
2960 185 100         if (entry->buf_pos < entry->buf_len) {
2961 163           line_start = entry->buffer + entry->buf_pos;
2962 163           newline = memchr(line_start, '\n', entry->buf_len - entry->buf_pos);
2963              
2964 163 100         if (newline) {
2965 139           line_len = newline - line_start;
2966 139           result = newSVpvn(line_start, line_len);
2967 139           entry->buf_pos += line_len + 1;
2968 139           ST(0) = sv_2mortal(result);
2969 139           XSRETURN(1);
2970             }
2971             }
2972              
2973             /* No newline found, need more data */
2974 46 100         if (entry->eof) {
2975             /* Return remaining data if any */
2976 14 100         if (entry->buf_pos < entry->buf_len) {
2977 11           line_len = entry->buf_len - entry->buf_pos;
2978 11           result = newSVpvn(entry->buffer + entry->buf_pos, line_len);
2979 11           entry->buf_pos = entry->buf_len;
2980 11           ST(0) = sv_2mortal(result);
2981 11           XSRETURN(1);
2982             }
2983 3           ST(0) = &PL_sv_undef;
2984 3           XSRETURN(1);
2985             }
2986              
2987             /* Move remaining data to start of buffer */
2988 32 100         if (entry->buf_pos > 0) {
2989 12           size_t remaining = entry->buf_len - entry->buf_pos;
2990 12 100         if (remaining > 0) {
2991 10           memmove(entry->buffer, entry->buffer + entry->buf_pos, remaining);
2992             }
2993 12           entry->buf_len = remaining;
2994 12           entry->buf_pos = 0;
2995             }
2996              
2997             /* Expand buffer if needed */
2998 32 100         if (entry->buf_len >= entry->buf_size - 1) {
2999 1           entry->buf_size *= 2;
3000 1           Renew(entry->buffer, entry->buf_size, char);
3001             }
3002              
3003             /* Read more data */
3004 32           n = read(entry->fd, entry->buffer + entry->buf_len,
3005 32           entry->buf_size - entry->buf_len - 1);
3006 32 50         if (n < 0) {
3007 0 0         if (errno == EINTR) continue;
3008 0           ST(0) = &PL_sv_undef;
3009 0           XSRETURN(1);
3010             }
3011 32 100         if (n == 0) {
3012 14           entry->eof = 1;
3013             } else {
3014 18           entry->buf_len += n;
3015             }
3016             }
3017             }
3018              
3019 123           XS_INTERNAL(xs_lines_iter_eof) {
3020 123           dXSARGS;
3021             SV *rv;
3022             IV idx;
3023             LineIterEntry *entry;
3024              
3025 123 50         if (items != 1) croak("Usage: $iter->eof");
3026              
3027 123           rv = ST(0);
3028 123 50         if (UNLIKELY(!SvROK(rv))) {
3029 0           croak("Invalid lines iterator object");
3030             }
3031              
3032             /* Direct IV access and inline eof check */
3033 123           idx = SvIV(SvRV(rv));
3034              
3035 123 50         if (UNLIKELY(idx < 0 || idx >= g_iters_count)) {
    50          
3036 0           ST(0) = &PL_sv_yes;
3037 0           XSRETURN(1);
3038             }
3039              
3040 123           entry = &g_iters[idx];
3041 123 100         ST(0) = (entry->eof && entry->buf_pos >= entry->buf_len) ? &PL_sv_yes : &PL_sv_no;
    50          
3042 123           XSRETURN(1);
3043             }
3044              
3045 18           XS_INTERNAL(xs_lines_iter_close) {
3046 18           dXSARGS;
3047             SV *rv, *inner;
3048             IV idx;
3049              
3050 18 50         if (items != 1) croak("Usage: $iter->close");
3051              
3052 18           rv = ST(0);
3053 18 50         if (UNLIKELY(!SvROK(rv))) {
3054 0           croak("Invalid lines iterator object");
3055             }
3056              
3057 18           inner = SvRV(rv);
3058 18           idx = SvIV(inner);
3059              
3060 18           file_lines_close(idx);
3061 18           sv_setiv(inner, -1); /* Mark as closed */
3062 18           XSRETURN_EMPTY;
3063             }
3064              
3065 17           XS_INTERNAL(xs_lines_iter_DESTROY) {
3066 17           dXSARGS;
3067             SV *rv;
3068             IV idx;
3069              
3070             PERL_UNUSED_VAR(items);
3071              
3072 17 50         if (PL_dirty) XSRETURN_EMPTY;
3073              
3074 17           rv = ST(0);
3075 17 50         if (UNLIKELY(!SvROK(rv))) {
3076 0           XSRETURN_EMPTY;
3077             }
3078              
3079 17           idx = SvIV(SvRV(rv));
3080              
3081 17 50         if (idx >= 0) {
3082 0           file_lines_close(idx);
3083             }
3084 17           XSRETURN_EMPTY;
3085             }
3086              
3087             /* ============================================
3088             Callback registry for line processing
3089             Allows C-level predicates for maximum speed
3090             ============================================ */
3091              
3092             /* Predicate function type for line processing */
3093             typedef bool (*file_line_predicate)(pTHX_ SV *line);
3094              
3095             /* Registered callback entry */
3096             typedef struct {
3097             file_line_predicate predicate; /* C function pointer (NULL for Perl-only) */
3098             SV *perl_callback; /* Perl callback (for fallback or custom) */
3099             } FileLineCallback;
3100              
3101             /* Global callback registry */
3102             static HV *g_file_callback_registry = NULL;
3103              
3104             /* Built-in C predicates */
3105 45           static bool pred_is_blank(pTHX_ SV *line) {
3106             STRLEN len;
3107 45           const char *s = SvPV(line, len);
3108             STRLEN i;
3109 60 100         for (i = 0; i < len; i++) {
3110 50 100         if (s[i] != ' ' && s[i] != '\t' && s[i] != '\r' && s[i] != '\n') {
    50          
    50          
    50          
3111 35           return FALSE;
3112             }
3113             }
3114 10           return TRUE;
3115             }
3116              
3117 36           static bool pred_is_not_blank(pTHX_ SV *line) {
3118 36           return !pred_is_blank(aTHX_ line);
3119             }
3120              
3121 9           static bool pred_is_empty(pTHX_ SV *line) {
3122 9           return SvCUR(line) == 0;
3123             }
3124              
3125 9           static bool pred_is_not_empty(pTHX_ SV *line) {
3126 9           return SvCUR(line) > 0;
3127             }
3128              
3129 24           static bool pred_is_comment(pTHX_ SV *line) {
3130             STRLEN len;
3131 24           const char *s = SvPV(line, len);
3132             /* Skip leading whitespace */
3133 33 100         while (len > 0 && (*s == ' ' || *s == '\t')) {
    100          
    50          
3134 9           s++;
3135 9           len--;
3136             }
3137 24 100         return len > 0 && *s == '#';
    100          
3138             }
3139              
3140 9           static bool pred_is_not_comment(pTHX_ SV *line) {
3141 9           return !pred_is_comment(aTHX_ line);
3142             }
3143              
3144             /* Cleanup callback registry during global destruction */
3145 18           static void file_cleanup_callback_registry(pTHX_ void *data) {
3146             PERL_UNUSED_ARG(data);
3147              
3148             /* During global destruction, just NULL out pointers.
3149             * Perl handles SV cleanup; trying to free them ourselves
3150             * can cause crashes due to destruction order. */
3151 18 50         if (PL_dirty) {
3152 18           g_file_callback_registry = NULL;
3153 18           return;
3154             }
3155              
3156             /* Normal cleanup - not during global destruction */
3157 0           g_file_callback_registry = NULL;
3158             }
3159              
3160 21           static void file_init_callback_registry(pTHX) {
3161             SV *sv;
3162             FileLineCallback *cb;
3163              
3164 21 100         if (g_file_callback_registry) return;
3165 18           g_file_callback_registry = newHV();
3166              
3167             /* Register built-in predicates with both naming conventions */
3168             /* blank / is_blank */
3169 18           Newxz(cb, 1, FileLineCallback);
3170 18           cb->predicate = pred_is_blank;
3171 18           cb->perl_callback = NULL;
3172 18           sv = newSViv(PTR2IV(cb));
3173 18           hv_store(g_file_callback_registry, "blank", 5, sv, 0);
3174 18           hv_store(g_file_callback_registry, "is_blank", 8, SvREFCNT_inc(sv), 0);
3175              
3176             /* not_blank / is_not_blank */
3177 18           Newxz(cb, 1, FileLineCallback);
3178 18           cb->predicate = pred_is_not_blank;
3179 18           cb->perl_callback = NULL;
3180 18           sv = newSViv(PTR2IV(cb));
3181 18           hv_store(g_file_callback_registry, "not_blank", 9, sv, 0);
3182 18           hv_store(g_file_callback_registry, "is_not_blank", 12, SvREFCNT_inc(sv), 0);
3183              
3184             /* empty / is_empty */
3185 18           Newxz(cb, 1, FileLineCallback);
3186 18           cb->predicate = pred_is_empty;
3187 18           cb->perl_callback = NULL;
3188 18           sv = newSViv(PTR2IV(cb));
3189 18           hv_store(g_file_callback_registry, "empty", 5, sv, 0);
3190 18           hv_store(g_file_callback_registry, "is_empty", 8, SvREFCNT_inc(sv), 0);
3191              
3192             /* not_empty / is_not_empty */
3193 18           Newxz(cb, 1, FileLineCallback);
3194 18           cb->predicate = pred_is_not_empty;
3195 18           cb->perl_callback = NULL;
3196 18           sv = newSViv(PTR2IV(cb));
3197 18           hv_store(g_file_callback_registry, "not_empty", 9, sv, 0);
3198 18           hv_store(g_file_callback_registry, "is_not_empty", 12, SvREFCNT_inc(sv), 0);
3199              
3200             /* comment / is_comment */
3201 18           Newxz(cb, 1, FileLineCallback);
3202 18           cb->predicate = pred_is_comment;
3203 18           cb->perl_callback = NULL;
3204 18           sv = newSViv(PTR2IV(cb));
3205 18           hv_store(g_file_callback_registry, "comment", 7, sv, 0);
3206 18           hv_store(g_file_callback_registry, "is_comment", 10, SvREFCNT_inc(sv), 0);
3207              
3208             /* not_comment / is_not_comment */
3209 18           Newxz(cb, 1, FileLineCallback);
3210 18           cb->predicate = pred_is_not_comment;
3211 18           cb->perl_callback = NULL;
3212 18           sv = newSViv(PTR2IV(cb));
3213 18           hv_store(g_file_callback_registry, "not_comment", 11, sv, 0);
3214 18           hv_store(g_file_callback_registry, "is_not_comment", 14, SvREFCNT_inc(sv), 0);
3215             }
3216              
3217 17           static FileLineCallback* file_get_callback(pTHX_ const char *name) {
3218             SV **svp;
3219 17 50         if (!g_file_callback_registry) return NULL;
3220 17           svp = hv_fetch(g_file_callback_registry, name, strlen(name), 0);
3221 17 100         if (svp && SvIOK(*svp)) {
    50          
3222 14           return INT2PTR(FileLineCallback*, SvIVX(*svp));
3223             }
3224 3           return NULL;
3225             }
3226              
3227             /* Process lines with callback - MULTICALL optimized (Perl >= 5.14 only) */
3228 7           XS_INTERNAL(xs_each_line) {
3229 7           dXSARGS;
3230             #if PERL_VERSION >= 14
3231             dMULTICALL;
3232             #endif
3233             const char *path;
3234             SV *callback;
3235             IV idx;
3236             CV *block_cv;
3237             SV *old_defsv;
3238             SV *line_sv;
3239             LineIterEntry *entry;
3240             char *line_start;
3241             char *newline;
3242             size_t line_len;
3243             ssize_t n;
3244             #if PERL_VERSION >= 14
3245 7           U8 gimme = G_VOID;
3246             #endif
3247              
3248 7 50         if (items != 2) croak("Usage: file::each_line(path, callback)");
3249              
3250 7           path = SvPV_nolen(ST(0));
3251 7           callback = ST(1);
3252              
3253 7 50         if (!SvROK(callback) || SvTYPE(SvRV(callback)) != SVt_PVCV) {
    50          
3254 0           croak("Second argument must be a code reference");
3255             }
3256              
3257 7           block_cv = (CV*)SvRV(callback);
3258 7           idx = file_lines_open(aTHX_ path);
3259 7 100         if (idx < 0) {
3260 1           XSRETURN_EMPTY;
3261             }
3262              
3263 6           entry = &g_iters[idx];
3264              
3265 6 50         old_defsv = DEFSV;
3266 6           line_sv = newSV(256);
3267 6 50         DEFSV = line_sv;
3268              
3269             #if PERL_VERSION >= 14
3270 6 50         PUSH_MULTICALL(block_cv);
3271             #endif
3272              
3273             while (1) {
3274             /* Look for newline in current buffer */
3275 1033 100         if (entry->buf_pos < entry->buf_len) {
3276 1024           line_start = entry->buffer + entry->buf_pos;
3277 1024           newline = memchr(line_start, '\n', entry->buf_len - entry->buf_pos);
3278              
3279 1024 100         if (newline) {
3280 1018           line_len = newline - line_start;
3281 1018           sv_setpvn(line_sv, line_start, line_len);
3282 1018           entry->buf_pos += line_len + 1;
3283             #if PERL_VERSION >= 14
3284 1018           MULTICALL;
3285             #else
3286             { dSP; PUSHMARK(SP); call_sv((SV*)block_cv, G_VOID|G_DISCARD); }
3287             #endif
3288 1017           continue;
3289             }
3290             }
3291              
3292             /* No newline found, need more data */
3293 15 100         if (entry->eof) {
3294             /* Return remaining data if any */
3295 5 100         if (entry->buf_pos < entry->buf_len) {
3296 3           line_len = entry->buf_len - entry->buf_pos;
3297 3           sv_setpvn(line_sv, entry->buffer + entry->buf_pos, line_len);
3298 3           entry->buf_pos = entry->buf_len;
3299             #if PERL_VERSION >= 14
3300 3           MULTICALL;
3301             #else
3302             { dSP; PUSHMARK(SP); call_sv((SV*)block_cv, G_VOID|G_DISCARD); }
3303             #endif
3304             }
3305 5           break;
3306             }
3307              
3308             /* Move remaining data to start of buffer */
3309 10 100         if (entry->buf_pos > 0) {
3310 4           size_t remaining = entry->buf_len - entry->buf_pos;
3311 4 100         if (remaining > 0) {
3312 3           memmove(entry->buffer, entry->buffer + entry->buf_pos, remaining);
3313             }
3314 4           entry->buf_len = remaining;
3315 4           entry->buf_pos = 0;
3316             }
3317              
3318             /* Expand buffer if needed */
3319 10 50         if (entry->buf_len >= entry->buf_size - 1) {
3320 0           entry->buf_size *= 2;
3321 0           Renew(entry->buffer, entry->buf_size, char);
3322             }
3323              
3324             /* Read more data */
3325 10           n = read(entry->fd, entry->buffer + entry->buf_len,
3326 10           entry->buf_size - entry->buf_len - 1);
3327 10 50         if (n < 0) {
3328 0 0         if (errno == EINTR) continue;
3329 0           break;
3330             }
3331 10 100         if (n == 0) {
3332 5           entry->eof = 1;
3333             } else {
3334 5           entry->buf_len += n;
3335             }
3336             }
3337              
3338             #if PERL_VERSION >= 14
3339 5 50         POP_MULTICALL;
3340             #endif
3341 5           SvREFCNT_dec(line_sv);
3342 5 50         DEFSV = old_defsv;
3343 5           file_lines_close(idx);
3344 5           XSRETURN_EMPTY;
3345             }
3346              
3347             /* Grep lines with callback or registered predicate name */
3348 17           XS_INTERNAL(xs_grep_lines) {
3349 17           dXSARGS;
3350             const char *path;
3351             SV *predicate;
3352             IV idx;
3353             SV *line;
3354             AV *result;
3355 17           CV *block_cv = NULL;
3356 17           FileLineCallback *fcb = NULL;
3357              
3358 17 50         if (items != 2) croak("Usage: file::grep_lines(path, &predicate or $name)");
3359              
3360 17           path = SvPV_nolen(ST(0));
3361 17           predicate = ST(1);
3362 17           result = newAV();
3363              
3364             /* Check if predicate is a name or coderef */
3365 17 100         if (SvROK(predicate) && SvTYPE(SvRV(predicate)) == SVt_PVCV) {
    50          
3366 5           block_cv = (CV*)SvRV(predicate);
3367             } else {
3368 12           const char *name = SvPV_nolen(predicate);
3369 12           fcb = file_get_callback(aTHX_ name);
3370 12 100         if (!fcb) {
3371 1           croak("File::Raw::grep_lines: unknown predicate '%s'", name);
3372             }
3373             }
3374              
3375 16           idx = file_lines_open(aTHX_ path);
3376 16 50         if (idx < 0) {
3377 0           ST(0) = sv_2mortal(newRV_noinc((SV*)result));
3378 0           XSRETURN(1);
3379             }
3380              
3381             /* C predicate path - fastest */
3382 16 100         if (fcb && fcb->predicate) {
    100          
3383 80 100         while ((line = file_lines_next(aTHX_ idx)) != &PL_sv_undef) {
3384 72 100         if (fcb->predicate(aTHX_ line)) {
3385 41           av_push(result, line);
3386             } else {
3387 31           SvREFCNT_dec(line);
3388             }
3389             }
3390 8           file_lines_close(idx);
3391 8           ST(0) = sv_2mortal(newRV_noinc((SV*)result));
3392 8           XSRETURN(1);
3393             }
3394              
3395             /* Call Perl callback */
3396             {
3397 8 100         SV *cb_sv = fcb ? fcb->perl_callback : (SV*)block_cv;
3398 1065 100         while ((line = file_lines_next(aTHX_ idx)) != &PL_sv_undef) {
3399 1057           dSP;
3400             IV count;
3401             SV *result_sv;
3402 1057           bool matches = FALSE;
3403 1057 50         PUSHMARK(SP);
3404 1057 50         XPUSHs(line);
3405 1057           PUTBACK;
3406 1057           count = call_sv(cb_sv, G_SCALAR);
3407 1057           SPAGAIN;
3408 1057 50         if (count > 0) {
3409 1057           result_sv = POPs;
3410 1057           matches = SvTRUE(result_sv);
3411             }
3412 1057           PUTBACK;
3413 1057 100         if (matches) {
3414 26           av_push(result, line);
3415             } else {
3416 1031           SvREFCNT_dec(line);
3417             }
3418             }
3419             }
3420              
3421 8           file_lines_close(idx);
3422 8           ST(0) = sv_2mortal(newRV_noinc((SV*)result));
3423 8           XSRETURN(1);
3424             }
3425              
3426             /* Count lines matching predicate */
3427 6           XS_INTERNAL(xs_count_lines) {
3428 6           dXSARGS;
3429             const char *path;
3430 6           SV *predicate = NULL;
3431             IV idx;
3432             SV *line;
3433 6           IV count = 0;
3434 6           CV *block_cv = NULL;
3435 6           FileLineCallback *fcb = NULL;
3436              
3437 6 50         if (items < 1 || items > 2) croak("Usage: file::count_lines(path, [&predicate or $name])");
    50          
3438              
3439 6           path = SvPV_nolen(ST(0));
3440              
3441             /* If no predicate, just count newlines - no SV creation needed */
3442 6 100         if (items == 1) {
3443             int fd;
3444             char *buffer;
3445 4           ssize_t n, total_read = 0;
3446             char *p, *end;
3447 4           char last_char = '\n'; /* Assume last char is newline (handles empty file) */
3448             #ifdef _WIN32
3449             int open_flags = O_RDONLY | O_BINARY;
3450             #else
3451 4           int open_flags = O_RDONLY;
3452             #endif
3453 4           fd = open(path, open_flags);
3454 4 100         if (UNLIKELY(fd < 0)) {
3455 1           ST(0) = sv_2mortal(newSViv(0));
3456 1           XSRETURN(1);
3457             }
3458              
3459 3           Newx(buffer, FILE_BUFFER_SIZE, char);
3460 3           count = 0;
3461              
3462 5 100         while ((n = read(fd, buffer, FILE_BUFFER_SIZE)) > 0) {
3463 2           p = buffer;
3464 2           end = buffer + n;
3465 1010 100         while ((p = memchr(p, '\n', end - p)) != NULL) {
3466 1008           count++;
3467 1008           p++;
3468             }
3469 2           total_read += n;
3470 2           last_char = buffer[n - 1];
3471             }
3472 3           close(fd);
3473 3           Safefree(buffer);
3474              
3475             /* If file doesn't end with newline, count the last line */
3476 3 100         if (total_read > 0 && last_char != '\n') {
    100          
3477 1           count++;
3478             }
3479              
3480 3           ST(0) = sv_2mortal(newSViv(count));
3481 3           XSRETURN(1);
3482             }
3483              
3484 2           predicate = ST(1);
3485              
3486             /* Check if predicate is a name or coderef */
3487 2 100         if (SvROK(predicate) && SvTYPE(SvRV(predicate)) == SVt_PVCV) {
    50          
3488 1           block_cv = (CV*)SvRV(predicate);
3489             } else {
3490 1           const char *name = SvPV_nolen(predicate);
3491 1           fcb = file_get_callback(aTHX_ name);
3492 1 50         if (!fcb) {
3493 0           croak("File::Raw::count_lines: unknown predicate '%s'", name);
3494             }
3495             }
3496              
3497 2           idx = file_lines_open(aTHX_ path);
3498 2 50         if (idx < 0) {
3499 0           ST(0) = sv_2mortal(newSViv(0));
3500 0           XSRETURN(1);
3501             }
3502              
3503             /* C predicate path - fastest */
3504 2 100         if (fcb && fcb->predicate) {
    50          
3505 10 100         while ((line = file_lines_next(aTHX_ idx)) != &PL_sv_undef) {
3506 9 100         if (fcb->predicate(aTHX_ line)) {
3507 7           count++;
3508             }
3509 9           SvREFCNT_dec(line);
3510             }
3511 1           file_lines_close(idx);
3512 1           ST(0) = sv_2mortal(newSViv(count));
3513 1           XSRETURN(1);
3514             }
3515              
3516             /* Call Perl callback */
3517             {
3518 1 50         SV *cb_sv = fcb ? fcb->perl_callback : (SV*)block_cv;
3519 10 100         while ((line = file_lines_next(aTHX_ idx)) != &PL_sv_undef) {
3520 9           dSP;
3521             IV n;
3522             SV *result_sv;
3523 9           bool matches = FALSE;
3524 9 50         PUSHMARK(SP);
3525 9 50         XPUSHs(line);
3526 9           PUTBACK;
3527 9           n = call_sv(cb_sv, G_SCALAR);
3528 9           SPAGAIN;
3529 9 50         if (n > 0) {
3530 9           result_sv = POPs;
3531 9           matches = SvTRUE(result_sv);
3532             }
3533 9           PUTBACK;
3534 9 100         if (matches) {
3535 8           count++;
3536             }
3537 9           SvREFCNT_dec(line);
3538             }
3539             }
3540              
3541 1           file_lines_close(idx);
3542 1           ST(0) = sv_2mortal(newSViv(count));
3543 1           XSRETURN(1);
3544             }
3545              
3546             /* Find first line matching predicate */
3547 5           XS_INTERNAL(xs_find_line) {
3548 5           dXSARGS;
3549             const char *path;
3550             SV *predicate;
3551             IV idx;
3552             SV *line;
3553 5           CV *block_cv = NULL;
3554 5           FileLineCallback *fcb = NULL;
3555              
3556 5 50         if (items != 2) croak("Usage: file::find_line(path, &predicate or $name)");
3557              
3558 5           path = SvPV_nolen(ST(0));
3559 5           predicate = ST(1);
3560              
3561             /* Check if predicate is a name or coderef */
3562 5 100         if (SvROK(predicate) && SvTYPE(SvRV(predicate)) == SVt_PVCV) {
    50          
3563 4           block_cv = (CV*)SvRV(predicate);
3564             } else {
3565 1           const char *name = SvPV_nolen(predicate);
3566 1           fcb = file_get_callback(aTHX_ name);
3567 1 50         if (!fcb) {
3568 0           croak("File::Raw::find_line: unknown predicate '%s'", name);
3569             }
3570             }
3571              
3572 5           idx = file_lines_open(aTHX_ path);
3573 5 50         if (idx < 0) {
3574 0           XSRETURN_UNDEF;
3575             }
3576              
3577             /* C predicate path - fastest */
3578 5 100         if (fcb && fcb->predicate) {
    50          
3579 6 50         while ((line = file_lines_next(aTHX_ idx)) != &PL_sv_undef) {
3580 6 100         if (fcb->predicate(aTHX_ line)) {
3581 1           file_lines_close(idx);
3582 1           ST(0) = sv_2mortal(line);
3583 1           XSRETURN(1);
3584             }
3585 5           SvREFCNT_dec(line);
3586             }
3587 0           file_lines_close(idx);
3588 0           XSRETURN_UNDEF;
3589             }
3590              
3591             /* Call Perl callback */
3592             {
3593 4 50         SV *cb_sv = fcb ? fcb->perl_callback : (SV*)block_cv;
3594 22 100         while ((line = file_lines_next(aTHX_ idx)) != &PL_sv_undef) {
3595 21           dSP;
3596             IV n;
3597             SV *result_sv;
3598 21           bool matches = FALSE;
3599 21 50         PUSHMARK(SP);
3600 21 50         XPUSHs(line);
3601 21           PUTBACK;
3602 21           n = call_sv(cb_sv, G_SCALAR);
3603 21           SPAGAIN;
3604 21 50         if (n > 0) {
3605 21           result_sv = POPs;
3606 21           matches = SvTRUE(result_sv);
3607             }
3608 21           PUTBACK;
3609 21 100         if (matches) {
3610 3           file_lines_close(idx);
3611 3           ST(0) = sv_2mortal(line);
3612 3           XSRETURN(1);
3613             }
3614 18           SvREFCNT_dec(line);
3615             }
3616             }
3617              
3618 1           file_lines_close(idx);
3619 1           XSRETURN_UNDEF;
3620             }
3621              
3622             /* Map lines with callback */
3623 4           XS_INTERNAL(xs_map_lines) {
3624 4           dXSARGS;
3625             const char *path;
3626             SV *callback;
3627             IV idx;
3628             SV *line;
3629             AV *result;
3630 4 50         if (items != 2) croak("Usage: file::map_lines(path, &callback)");
3631              
3632 4           path = SvPV_nolen(ST(0));
3633 4           callback = ST(1);
3634 4           result = newAV();
3635              
3636 4 50         if (!SvROK(callback) || SvTYPE(SvRV(callback)) != SVt_PVCV) {
    50          
3637 0           croak("Second argument must be a code reference");
3638             }
3639              
3640 4           idx = file_lines_open(aTHX_ path);
3641 4 50         if (idx < 0) {
3642 0           ST(0) = sv_2mortal(newRV_noinc((SV*)result));
3643 0           XSRETURN(1);
3644             }
3645              
3646             /* Call Perl callback */
3647             {
3648 31 100         while ((line = file_lines_next(aTHX_ idx)) != &PL_sv_undef) {
3649 27           dSP;
3650             IV count;
3651             SV *result_sv;
3652 27 50         PUSHMARK(SP);
3653 27 50         XPUSHs(sv_2mortal(line));
3654 27           PUTBACK;
3655 27           count = call_sv(callback, G_SCALAR);
3656 27           SPAGAIN;
3657 27 50         if (count > 0) {
3658 27           result_sv = POPs;
3659 27           av_push(result, SvREFCNT_inc(result_sv));
3660             }
3661 27           PUTBACK;
3662             }
3663             }
3664              
3665 4           file_lines_close(idx);
3666 4           ST(0) = sv_2mortal(newRV_noinc((SV*)result));
3667 4           XSRETURN(1);
3668             }
3669              
3670             /* Register a Perl callback */
3671 4           XS_INTERNAL(xs_register_line_callback) {
3672 4           dXSARGS;
3673             const char *name;
3674             STRLEN name_len;
3675             SV *coderef;
3676             FileLineCallback *cb;
3677             SV *sv;
3678              
3679 4 50         if (items != 2) croak("Usage: file::register_line_callback($name, \\&coderef)");
3680              
3681 4           name = SvPV(ST(0), name_len);
3682 4           coderef = ST(1);
3683              
3684 4 100         if (!SvROK(coderef) || SvTYPE(SvRV(coderef)) != SVt_PVCV) {
    50          
3685 1           croak("File::Raw::register_line_callback: second argument must be a coderef");
3686             }
3687              
3688 3           file_init_callback_registry(aTHX);
3689              
3690             /* If already registered, just update the perl_callback in place */
3691             {
3692 3           FileLineCallback *existing = file_get_callback(aTHX_ name);
3693 3 100         if (existing) {
3694             /* Update existing - free old perl_callback and set new one */
3695 1 50         if (existing->perl_callback) {
3696 1           SvREFCNT_dec(existing->perl_callback);
3697             }
3698 1           existing->perl_callback = newSVsv(coderef);
3699 1           existing->predicate = NULL; /* Clear any C predicate */
3700 1           XSRETURN_YES;
3701             }
3702             }
3703              
3704 2           Newxz(cb, 1, FileLineCallback);
3705 2           cb->predicate = NULL; /* No C function */
3706 2           cb->perl_callback = newSVsv(coderef);
3707              
3708 2           sv = newSViv(PTR2IV(cb));
3709 2           hv_store(g_file_callback_registry, name, name_len, sv, 0);
3710              
3711 2           XSRETURN_YES;
3712             }
3713              
3714             /* List registered callbacks */
3715 1           XS_INTERNAL(xs_list_line_callbacks) {
3716 1           dXSARGS;
3717             AV *result;
3718             HE *entry;
3719              
3720             PERL_UNUSED_VAR(items);
3721              
3722 1           result = newAV();
3723 1 50         if (g_file_callback_registry) {
3724 1           hv_iterinit(g_file_callback_registry);
3725 15 100         while ((entry = hv_iternext(g_file_callback_registry))) {
3726 14           av_push(result, newSVsv(hv_iterkeysv(entry)));
3727             }
3728             }
3729              
3730 1           ST(0) = sv_2mortal(newRV_noinc((SV*)result));
3731 1           XSRETURN(1);
3732             }
3733              
3734             /* ============================================
3735             Hook registration XS functions
3736             ============================================ */
3737              
3738             /* Register a Perl read hook */
3739 11           XS_INTERNAL(xs_register_read_hook) {
3740 11           dXSARGS;
3741             SV *coderef;
3742             FileHookEntry *entry;
3743              
3744 11 50         if (items != 1) croak("Usage: file::register_read_hook(\\&coderef)");
3745              
3746 11           coderef = ST(0);
3747 11 50         if (!SvROK(coderef) || SvTYPE(SvRV(coderef)) != SVt_PVCV) {
    50          
3748 0           croak("File::Raw::register_read_hook: argument must be a coderef");
3749             }
3750              
3751             /* Use the hook list for Perl callbacks */
3752 11           Newxz(entry, 1, FileHookEntry);
3753 11           entry->name = "perl_read_hook";
3754 11           entry->c_func = NULL;
3755 11           entry->perl_callback = newSVsv(coderef);
3756 11           entry->priority = FILE_HOOK_PRIORITY_NORMAL;
3757 11           entry->user_data = NULL;
3758 11           entry->next = g_file_hooks[FILE_HOOK_PHASE_READ];
3759 11           g_file_hooks[FILE_HOOK_PHASE_READ] = entry;
3760              
3761 11           XSRETURN_YES;
3762             }
3763              
3764             /* Register a Perl write hook */
3765 4           XS_INTERNAL(xs_register_write_hook) {
3766 4           dXSARGS;
3767             SV *coderef;
3768             FileHookEntry *entry;
3769              
3770 4 50         if (items != 1) croak("Usage: file::register_write_hook(\\&coderef)");
3771              
3772 4           coderef = ST(0);
3773 4 50         if (!SvROK(coderef) || SvTYPE(SvRV(coderef)) != SVt_PVCV) {
    50          
3774 0           croak("File::Raw::register_write_hook: argument must be a coderef");
3775             }
3776              
3777             /* Use the hook list for Perl callbacks */
3778 4           Newxz(entry, 1, FileHookEntry);
3779 4           entry->name = "perl_write_hook";
3780 4           entry->c_func = NULL;
3781 4           entry->perl_callback = newSVsv(coderef);
3782 4           entry->priority = FILE_HOOK_PRIORITY_NORMAL;
3783 4           entry->user_data = NULL;
3784 4           entry->next = g_file_hooks[FILE_HOOK_PHASE_WRITE];
3785 4           g_file_hooks[FILE_HOOK_PHASE_WRITE] = entry;
3786              
3787 4           XSRETURN_YES;
3788             }
3789              
3790             /* Clear all hooks for a phase */
3791 40           XS_INTERNAL(xs_clear_hooks) {
3792 40           dXSARGS;
3793             const char *phase_name;
3794             FileHookPhase phase;
3795             FileHookEntry *entry, *next;
3796              
3797 40 50         if (items != 1) croak("Usage: file::clear_hooks($phase)");
3798              
3799 40           phase_name = SvPV_nolen(ST(0));
3800              
3801 40 100         if (strcmp(phase_name, "read") == 0) {
3802 26           phase = FILE_HOOK_PHASE_READ;
3803 26           g_file_read_hook = NULL;
3804 26           g_file_read_hook_data = NULL;
3805 14 50         } else if (strcmp(phase_name, "write") == 0) {
3806 14           phase = FILE_HOOK_PHASE_WRITE;
3807 14           g_file_write_hook = NULL;
3808 14           g_file_write_hook_data = NULL;
3809 0 0         } else if (strcmp(phase_name, "open") == 0) {
3810 0           phase = FILE_HOOK_PHASE_OPEN;
3811 0 0         } else if (strcmp(phase_name, "close") == 0) {
3812 0           phase = FILE_HOOK_PHASE_CLOSE;
3813             } else {
3814 0           croak("File::Raw::clear_hooks: unknown phase '%s' (use read, write, open, close)", phase_name);
3815             }
3816              
3817             /* Free hook list */
3818 40           entry = g_file_hooks[phase];
3819 55 100         while (entry) {
3820 15           next = entry->next;
3821 15 50         if (entry->perl_callback) {
3822 15           SvREFCNT_dec(entry->perl_callback);
3823             }
3824 15           Safefree(entry);
3825 15           entry = next;
3826             }
3827 40           g_file_hooks[phase] = NULL;
3828              
3829 40           XSRETURN_YES;
3830             }
3831              
3832             /* Check if hooks are registered for a phase */
3833 11           XS_INTERNAL(xs_has_hooks) {
3834 11           dXSARGS;
3835             const char *phase_name;
3836             FileHookPhase phase;
3837             int has;
3838              
3839 11 50         if (items != 1) croak("Usage: file::has_hooks($phase)");
3840              
3841 11           phase_name = SvPV_nolen(ST(0));
3842              
3843 11 100         if (strcmp(phase_name, "read") == 0) {
3844 6           phase = FILE_HOOK_PHASE_READ;
3845 6 100         has = (g_file_read_hook != NULL) || (g_file_hooks[phase] != NULL);
    100          
3846 5 50         } else if (strcmp(phase_name, "write") == 0) {
3847 5           phase = FILE_HOOK_PHASE_WRITE;
3848 5 50         has = (g_file_write_hook != NULL) || (g_file_hooks[phase] != NULL);
    100          
3849 0 0         } else if (strcmp(phase_name, "open") == 0) {
3850 0           phase = FILE_HOOK_PHASE_OPEN;
3851 0           has = (g_file_hooks[phase] != NULL);
3852 0 0         } else if (strcmp(phase_name, "close") == 0) {
3853 0           phase = FILE_HOOK_PHASE_CLOSE;
3854 0           has = (g_file_hooks[phase] != NULL);
3855             } else {
3856 0           croak("File::Raw::has_hooks: unknown phase '%s' (use read, write, open, close)", phase_name);
3857             }
3858              
3859 11 100         ST(0) = has ? &PL_sv_yes : &PL_sv_no;
3860 11           XSRETURN(1);
3861             }
3862              
3863             /* New stat functions */
3864 2           XS_INTERNAL(xs_atime) {
3865 2           dXSARGS;
3866             const char *path;
3867 2 50         if (items != 1) croak("Usage: file::atime(path)");
3868 2           path = SvPV_nolen(ST(0));
3869 2           ST(0) = sv_2mortal(newSViv(file_atime_internal(path)));
3870 2           XSRETURN(1);
3871             }
3872              
3873 2           XS_INTERNAL(xs_ctime) {
3874 2           dXSARGS;
3875             const char *path;
3876 2 50         if (items != 1) croak("Usage: file::ctime(path)");
3877 2           path = SvPV_nolen(ST(0));
3878 2           ST(0) = sv_2mortal(newSViv(file_ctime_internal(path)));
3879 2           XSRETURN(1);
3880             }
3881              
3882 2           XS_INTERNAL(xs_mode) {
3883 2           dXSARGS;
3884             const char *path;
3885 2 50         if (items != 1) croak("Usage: file::mode(path)");
3886 2           path = SvPV_nolen(ST(0));
3887 2           ST(0) = sv_2mortal(newSViv(file_mode_internal(path)));
3888 2           XSRETURN(1);
3889             }
3890              
3891             /* Combined stat - all attributes in one syscall */
3892 4           XS_INTERNAL(xs_stat_all) {
3893 4           dXSARGS;
3894             const char *path;
3895             HV *result;
3896 4 50         if (items != 1) croak("Usage: File::Raw::stat(path)");
3897 4           path = SvPV_nolen(ST(0));
3898 4           result = file_stat_all_internal(aTHX_ path);
3899 4 100         if (result == NULL) {
3900 1           ST(0) = &PL_sv_undef;
3901             } else {
3902 3           ST(0) = sv_2mortal(newRV_noinc((SV*)result));
3903             }
3904 4           XSRETURN(1);
3905             }
3906              
3907 4           XS_INTERNAL(xs_is_link) {
3908 4           dXSARGS;
3909             const char *path;
3910 4 50         if (items != 1) croak("Usage: file::is_link(path)");
3911 4           path = SvPV_nolen(ST(0));
3912 4 100         ST(0) = file_is_link_internal(path) ? &PL_sv_yes : &PL_sv_no;
3913 4           XSRETURN(1);
3914             }
3915              
3916 0           XS_INTERNAL(xs_is_executable) {
3917 0           dXSARGS;
3918             const char *path;
3919 0 0         if (items != 1) croak("Usage: file::is_executable(path)");
3920 0           path = SvPV_nolen(ST(0));
3921 0 0         ST(0) = file_is_executable_internal(path) ? &PL_sv_yes : &PL_sv_no;
3922 0           XSRETURN(1);
3923             }
3924              
3925             /* File manipulation functions */
3926 4           XS_INTERNAL(xs_unlink) {
3927 4           dXSARGS;
3928             const char *path;
3929 4 50         if (items != 1) croak("Usage: file::unlink(path)");
3930 4           path = SvPV_nolen(ST(0));
3931 4 100         ST(0) = file_unlink_internal(path) ? &PL_sv_yes : &PL_sv_no;
3932 4           XSRETURN(1);
3933             }
3934              
3935 3           XS_INTERNAL(xs_copy) {
3936 3           dXSARGS;
3937             const char *src;
3938             const char *dst;
3939 3 50         if (items != 2) croak("Usage: file::copy(src, dst)");
3940 3           src = SvPV_nolen(ST(0));
3941 3           dst = SvPV_nolen(ST(1));
3942 3 100         ST(0) = file_copy_internal(aTHX_ src, dst) ? &PL_sv_yes : &PL_sv_no;
3943 3           XSRETURN(1);
3944             }
3945              
3946 3           XS_INTERNAL(xs_move) {
3947 3           dXSARGS;
3948             const char *src;
3949             const char *dst;
3950 3 50         if (items != 2) croak("Usage: file::move(src, dst)");
3951 3           src = SvPV_nolen(ST(0));
3952 3           dst = SvPV_nolen(ST(1));
3953 3 100         ST(0) = file_move_internal(aTHX_ src, dst) ? &PL_sv_yes : &PL_sv_no;
3954 3           XSRETURN(1);
3955             }
3956              
3957 2           XS_INTERNAL(xs_touch) {
3958 2           dXSARGS;
3959             const char *path;
3960 2 50         if (items != 1) croak("Usage: file::touch(path)");
3961 2           path = SvPV_nolen(ST(0));
3962 2 50         ST(0) = file_touch_internal(path) ? &PL_sv_yes : &PL_sv_no;
3963 2           XSRETURN(1);
3964             }
3965              
3966 1           XS_INTERNAL(xs_clear_stat_cache) {
3967 1           dXSARGS;
3968 1 50         if (items > 1) croak("Usage: file::clear_stat_cache() or file::clear_stat_cache(path)");
3969            
3970 1 50         if (items == 1 && SvOK(ST(0))) {
    0          
3971 0           const char *path = SvPV_nolen(ST(0));
3972 0           invalidate_stat_cache_path(path);
3973             } else {
3974 1           invalidate_stat_cache();
3975             }
3976            
3977 1           ST(0) = &PL_sv_yes;
3978 1           XSRETURN(1);
3979             }
3980              
3981 1           XS_INTERNAL(xs_chmod) {
3982 1           dXSARGS;
3983             const char *path;
3984             int mode;
3985 1 50         if (items != 2) croak("Usage: file::chmod(path, mode)");
3986 1           path = SvPV_nolen(ST(0));
3987 1           mode = SvIV(ST(1));
3988 1 50         ST(0) = file_chmod_internal(path, mode) ? &PL_sv_yes : &PL_sv_no;
3989 1           XSRETURN(1);
3990             }
3991              
3992 12           XS_INTERNAL(xs_mkdir) {
3993 12           dXSARGS;
3994             const char *path;
3995 12           int mode = 0755;
3996 12 50         if (items < 1 || items > 2) croak("Usage: file::mkdir(path, [mode])");
    50          
3997 12           path = SvPV_nolen(ST(0));
3998 12 50         if (items > 1) mode = SvIV(ST(1));
3999 12 100         ST(0) = file_mkdir_internal(path, mode) ? &PL_sv_yes : &PL_sv_no;
4000 12           XSRETURN(1);
4001             }
4002              
4003 6           XS_INTERNAL(xs_rmdir) {
4004 6           dXSARGS;
4005             const char *path;
4006 6 50         if (items != 1) croak("Usage: file::rmdir(path)");
4007 6           path = SvPV_nolen(ST(0));
4008 6 100         ST(0) = file_rmdir_internal(path) ? &PL_sv_yes : &PL_sv_no;
4009 6           XSRETURN(1);
4010             }
4011              
4012 5           XS_INTERNAL(xs_readdir) {
4013 5           dXSARGS;
4014             const char *path;
4015             AV *result;
4016 5 50         if (items != 1) croak("Usage: file::readdir(path)");
4017 5           path = SvPV_nolen(ST(0));
4018 5           result = file_readdir_internal(aTHX_ path);
4019 5           ST(0) = sv_2mortal(newRV_noinc((SV*)result));
4020 5           XSRETURN(1);
4021             }
4022              
4023             /* Path manipulation functions */
4024 12           XS_INTERNAL(xs_basename) {
4025 12           dXSARGS;
4026             const char *path;
4027 12 50         if (items != 1) croak("Usage: file::basename(path)");
4028 12           path = SvPV_nolen(ST(0));
4029 12           ST(0) = sv_2mortal(file_basename_internal(aTHX_ path));
4030 12           XSRETURN(1);
4031             }
4032              
4033 10           XS_INTERNAL(xs_dirname) {
4034 10           dXSARGS;
4035             const char *path;
4036 10 50         if (items != 1) croak("Usage: file::dirname(path)");
4037 10           path = SvPV_nolen(ST(0));
4038 10           ST(0) = sv_2mortal(file_dirname_internal(aTHX_ path));
4039 10           XSRETURN(1);
4040             }
4041              
4042 13           XS_INTERNAL(xs_extname) {
4043 13           dXSARGS;
4044             const char *path;
4045 13 50         if (items != 1) croak("Usage: file::extname(path)");
4046 13           path = SvPV_nolen(ST(0));
4047 13           ST(0) = sv_2mortal(file_extname_internal(aTHX_ path));
4048 13           XSRETURN(1);
4049             }
4050              
4051 11           XS_INTERNAL(xs_join) {
4052 11           dXSARGS;
4053             AV *parts;
4054             SSize_t i;
4055              
4056 11 50         if (items < 1) croak("Usage: file::join(part1, part2, ...)");
4057              
4058 11           parts = newAV();
4059 37 100         for (i = 0; i < items; i++) {
4060 26           av_push(parts, newSVsv(ST(i)));
4061             }
4062              
4063 11           ST(0) = sv_2mortal(file_join_internal(aTHX_ parts));
4064 11           SvREFCNT_dec((SV*)parts);
4065 11           XSRETURN(1);
4066             }
4067              
4068             /* mkpath: recursive mkdir */
4069 1           XS_INTERNAL(xs_mkpath) {
4070 1           dXSARGS;
4071             const char *path;
4072             STRLEN path_len;
4073             char buf[4096];
4074             STRLEN i;
4075 1           int created = 0;
4076              
4077 1 50         if (items != 1) croak("Usage: file_mkpath(path)");
4078 1           path = SvPV(ST(0), path_len);
4079 1 50         if (path_len >= sizeof(buf)) croak("Path too long");
4080              
4081 36 100         for (i = 0; i <= path_len; i++) {
4082 35 100         if (i == path_len || path[i] == '/' || path[i] == '\\') {
    100          
    50          
4083 5 100         if (i == 0) {
4084             /* Root / or drive-relative */
4085 1           buf[0] = path[0];
4086 1           buf[1] = '\0';
4087 1           continue;
4088             }
4089 4           memcpy(buf, path, i);
4090 4           buf[i] = '\0';
4091              
4092             /* Skip drive letter portion like C: */
4093 4 50         if (i == 2 && buf[1] == ':') continue;
    0          
4094              
4095 4 100         if (!file_is_dir_internal(buf)) {
4096 2 50         if (file_mkdir_internal(buf, 0755))
4097 2           created = 1;
4098             }
4099             }
4100             }
4101              
4102 1 50         ST(0) = created || file_is_dir_internal(path) ? &PL_sv_yes : &PL_sv_no;
    0          
4103 1           XSRETURN(1);
4104             }
4105              
4106             /* rm_rf: recursive remove directory */
4107 2           static void file_rm_rf_internal(pTHX_ const char *path) {
4108             AV *entries;
4109             SSize_t i, len;
4110              
4111 2 50         if (!file_is_dir_internal(path)) {
4112 0           file_unlink_internal(path);
4113 0           return;
4114             }
4115              
4116 2           entries = file_readdir_internal(aTHX_ path);
4117 2           len = av_len(entries) + 1;
4118 3 100         for (i = 0; i < len; i++) {
4119 1           SV **sv = av_fetch(entries, i, 0);
4120 1 50         if (sv) {
4121 1           AV *join_parts = newAV();
4122             SV *child_sv;
4123             const char *child;
4124              
4125 1           av_push(join_parts, newSVpv(path, 0));
4126 1           av_push(join_parts, newSVsv(*sv));
4127 1           child_sv = file_join_internal(aTHX_ join_parts);
4128 1           child = SvPV_nolen(child_sv);
4129              
4130 1 50         if (file_is_dir_internal(child)) {
4131 1           file_rm_rf_internal(aTHX_ child);
4132             } else {
4133 0           file_unlink_internal(child);
4134             }
4135              
4136 1           SvREFCNT_dec(child_sv);
4137 1           SvREFCNT_dec((SV*)join_parts);
4138             }
4139             }
4140 2           SvREFCNT_dec((SV*)entries);
4141 2           file_rmdir_internal(path);
4142             }
4143              
4144 1           XS_INTERNAL(xs_rm_rf) {
4145 1           dXSARGS;
4146             const char *path;
4147              
4148 1 50         if (items != 1) croak("Usage: file_rm_rf(path)");
4149 1           path = SvPV_nolen(ST(0));
4150 1           file_rm_rf_internal(aTHX_ path);
4151 1           ST(0) = &PL_sv_yes;
4152 1           XSRETURN(1);
4153             }
4154              
4155             /* Head and tail */
4156 6           XS_INTERNAL(xs_head) {
4157 6           dXSARGS;
4158             const char *path;
4159             AV *result;
4160 6           IV n = 10; /* Default to 10 lines */
4161 6 50         if (items < 1 || items > 2) croak("Usage: file::head(path, [n])");
    50          
4162 6           path = SvPV_nolen(ST(0));
4163 6 100         if (items > 1) n = SvIV(ST(1));
4164 6           result = file_head_internal(aTHX_ path, n);
4165 6           ST(0) = sv_2mortal(newRV_noinc((SV*)result));
4166 6           XSRETURN(1);
4167             }
4168              
4169 6           XS_INTERNAL(xs_tail) {
4170 6           dXSARGS;
4171             const char *path;
4172             AV *result;
4173 6           IV n = 10; /* Default to 10 lines */
4174 6 50         if (items < 1 || items > 2) croak("Usage: file::tail(path, [n])");
    50          
4175 6           path = SvPV_nolen(ST(0));
4176 6 100         if (items > 1) n = SvIV(ST(1));
4177 6           result = file_tail_internal(aTHX_ path, n);
4178 6           ST(0) = sv_2mortal(newRV_noinc((SV*)result));
4179 6           XSRETURN(1);
4180             }
4181              
4182             /* Atomic spew */
4183 14           XS_INTERNAL(xs_atomic_spew) {
4184 14           dXSARGS;
4185             const char *path;
4186 14 50         if (items != 2) croak("Usage: file::atomic_spew(path, data)");
4187 14           path = SvPV_nolen(ST(0));
4188 14 50         ST(0) = file_atomic_spew_internal(aTHX_ path, ST(1)) ? &PL_sv_yes : &PL_sv_no;
4189 14           XSRETURN(1);
4190             }
4191              
4192             /* ============================================
4193             Function-style XS (for import)
4194             ============================================ */
4195              
4196 0           XS_EXTERNAL(XS_file_func_slurp) {
4197 0           dXSARGS;
4198             const char *path;
4199 0 0         if (items != 1) croak("Usage: file_slurp($path)");
4200 0           path = SvPV_nolen(ST(0));
4201 0           ST(0) = sv_2mortal(file_slurp_internal(aTHX_ path));
4202 0           XSRETURN(1);
4203             }
4204              
4205 0           XS_EXTERNAL(XS_file_func_spew) {
4206 0           dXSARGS;
4207             const char *path;
4208 0 0         if (items != 2) croak("Usage: file_spew($path, $data)");
4209 0           path = SvPV_nolen(ST(0));
4210 0 0         if (file_spew_internal(aTHX_ path, ST(1))) {
4211 0           ST(0) = &PL_sv_yes;
4212             } else {
4213 0           ST(0) = &PL_sv_no;
4214             }
4215 0           XSRETURN(1);
4216             }
4217              
4218 0           XS_EXTERNAL(XS_file_func_exists) {
4219 0           dXSARGS;
4220             const char *path;
4221 0 0         if (items != 1) croak("Usage: file_exists($path)");
4222 0           path = SvPV_nolen(ST(0));
4223 0 0         ST(0) = file_exists_internal(path) ? &PL_sv_yes : &PL_sv_no;
4224 0           XSRETURN(1);
4225             }
4226              
4227 0           XS_EXTERNAL(XS_file_func_size) {
4228 0           dXSARGS;
4229             const char *path;
4230 0 0         if (items != 1) croak("Usage: file_size($path)");
4231 0           path = SvPV_nolen(ST(0));
4232 0           ST(0) = sv_2mortal(newSViv(file_size_internal(path)));
4233 0           XSRETURN(1);
4234             }
4235              
4236 0           XS_EXTERNAL(XS_file_func_is_file) {
4237 0           dXSARGS;
4238             const char *path;
4239 0 0         if (items != 1) croak("Usage: file_is_file($path)");
4240 0           path = SvPV_nolen(ST(0));
4241 0 0         ST(0) = file_is_file_internal(path) ? &PL_sv_yes : &PL_sv_no;
4242 0           XSRETURN(1);
4243             }
4244              
4245 0           XS_EXTERNAL(XS_file_func_is_dir) {
4246 0           dXSARGS;
4247             const char *path;
4248 0 0         if (items != 1) croak("Usage: file_is_dir($path)");
4249 0           path = SvPV_nolen(ST(0));
4250 0 0         ST(0) = file_is_dir_internal(path) ? &PL_sv_yes : &PL_sv_no;
4251 0           XSRETURN(1);
4252             }
4253              
4254 0           XS_EXTERNAL(XS_file_func_lines) {
4255 0           dXSARGS;
4256             const char *path;
4257             SV *content;
4258             AV *lines;
4259 0 0         if (items != 1) croak("Usage: file_lines($path)");
4260 0           path = SvPV_nolen(ST(0));
4261 0           content = file_slurp_internal(aTHX_ path);
4262              
4263 0 0         if (content == &PL_sv_undef) {
4264 0           lines = newAV();
4265             } else {
4266 0           lines = file_split_lines(aTHX_ content);
4267 0           SvREFCNT_dec(content);
4268             }
4269              
4270 0           ST(0) = sv_2mortal(newRV_noinc((SV*)lines));
4271 0           XSRETURN(1);
4272             }
4273              
4274 0           XS_EXTERNAL(XS_file_func_unlink) {
4275 0           dXSARGS;
4276             const char *path;
4277 0 0         if (items != 1) croak("Usage: file_unlink($path)");
4278 0           path = SvPV_nolen(ST(0));
4279 0 0         ST(0) = file_unlink_internal(path) ? &PL_sv_yes : &PL_sv_no;
4280 0           XSRETURN(1);
4281             }
4282              
4283 0           XS_EXTERNAL(XS_file_func_mkdir) {
4284 0           dXSARGS;
4285             const char *path;
4286 0 0         if (items != 1) croak("Usage: file_mkdir($path)");
4287 0           path = SvPV_nolen(ST(0));
4288 0 0         ST(0) = file_mkdir_internal(path, 0755) ? &PL_sv_yes : &PL_sv_no;
4289 0           XSRETURN(1);
4290             }
4291              
4292 0           XS_EXTERNAL(XS_file_func_rmdir) {
4293 0           dXSARGS;
4294             const char *path;
4295 0 0         if (items != 1) croak("Usage: file_rmdir($path)");
4296 0           path = SvPV_nolen(ST(0));
4297 0 0         ST(0) = file_rmdir_internal(path) ? &PL_sv_yes : &PL_sv_no;
4298 0           XSRETURN(1);
4299             }
4300              
4301 0           XS_EXTERNAL(XS_file_func_touch) {
4302 0           dXSARGS;
4303             const char *path;
4304 0 0         if (items != 1) croak("Usage: file_touch($path)");
4305 0           path = SvPV_nolen(ST(0));
4306 0 0         ST(0) = file_touch_internal(path) ? &PL_sv_yes : &PL_sv_no;
4307 0           XSRETURN(1);
4308             }
4309              
4310 0           XS_EXTERNAL(XS_file_func_clear_stat_cache) {
4311 0           dXSARGS;
4312 0 0         if (items > 1) croak("Usage: file_clear_stat_cache() or file_clear_stat_cache($path)");
4313            
4314 0 0         if (items == 1 && SvOK(ST(0))) {
    0          
4315 0           const char *path = SvPV_nolen(ST(0));
4316 0           invalidate_stat_cache_path(path);
4317             } else {
4318 0           invalidate_stat_cache();
4319             }
4320            
4321 0           ST(0) = &PL_sv_yes;
4322 0           XSRETURN(1);
4323             }
4324              
4325 0           XS_EXTERNAL(XS_file_func_basename) {
4326 0           dXSARGS;
4327             const char *path;
4328 0 0         if (items != 1) croak("Usage: file_basename($path)");
4329 0           path = SvPV_nolen(ST(0));
4330 0           ST(0) = sv_2mortal(file_basename_internal(aTHX_ path));
4331 0           XSRETURN(1);
4332             }
4333              
4334 0           XS_EXTERNAL(XS_file_func_dirname) {
4335 0           dXSARGS;
4336             const char *path;
4337 0 0         if (items != 1) croak("Usage: file_dirname($path)");
4338 0           path = SvPV_nolen(ST(0));
4339 0           ST(0) = sv_2mortal(file_dirname_internal(aTHX_ path));
4340 0           XSRETURN(1);
4341             }
4342              
4343 0           XS_EXTERNAL(XS_file_func_extname) {
4344 0           dXSARGS;
4345             const char *path;
4346 0 0         if (items != 1) croak("Usage: file_extname($path)");
4347 0           path = SvPV_nolen(ST(0));
4348 0           ST(0) = sv_2mortal(file_extname_internal(aTHX_ path));
4349 0           XSRETURN(1);
4350             }
4351              
4352 0           XS_EXTERNAL(XS_file_func_mtime) {
4353 0           dXSARGS;
4354             const char *path;
4355 0 0         if (items != 1) croak("Usage: file_mtime($path)");
4356 0           path = SvPV_nolen(ST(0));
4357 0           ST(0) = sv_2mortal(newSViv(file_mtime_internal(path)));
4358 0           XSRETURN(1);
4359             }
4360              
4361 0           XS_EXTERNAL(XS_file_func_atime) {
4362 0           dXSARGS;
4363             const char *path;
4364 0 0         if (items != 1) croak("Usage: file_atime($path)");
4365 0           path = SvPV_nolen(ST(0));
4366 0           ST(0) = sv_2mortal(newSViv(file_atime_internal(path)));
4367 0           XSRETURN(1);
4368             }
4369              
4370 0           XS_EXTERNAL(XS_file_func_ctime) {
4371 0           dXSARGS;
4372             const char *path;
4373 0 0         if (items != 1) croak("Usage: file_ctime($path)");
4374 0           path = SvPV_nolen(ST(0));
4375 0           ST(0) = sv_2mortal(newSViv(file_ctime_internal(path)));
4376 0           XSRETURN(1);
4377             }
4378              
4379 0           XS_EXTERNAL(XS_file_func_mode) {
4380 0           dXSARGS;
4381             const char *path;
4382 0 0         if (items != 1) croak("Usage: file_mode($path)");
4383 0           path = SvPV_nolen(ST(0));
4384 0           ST(0) = sv_2mortal(newSViv(file_mode_internal(path)));
4385 0           XSRETURN(1);
4386             }
4387              
4388 0           XS_EXTERNAL(XS_file_func_is_link) {
4389 0           dXSARGS;
4390             const char *path;
4391 0 0         if (items != 1) croak("Usage: file_is_link($path)");
4392 0           path = SvPV_nolen(ST(0));
4393 0 0         ST(0) = file_is_link_internal(path) ? &PL_sv_yes : &PL_sv_no;
4394 0           XSRETURN(1);
4395             }
4396              
4397 0           XS_EXTERNAL(XS_file_func_is_readable) {
4398 0           dXSARGS;
4399             const char *path;
4400 0 0         if (items != 1) croak("Usage: file_is_readable($path)");
4401 0           path = SvPV_nolen(ST(0));
4402 0 0         ST(0) = file_is_readable_internal(path) ? &PL_sv_yes : &PL_sv_no;
4403 0           XSRETURN(1);
4404             }
4405              
4406 0           XS_EXTERNAL(XS_file_func_is_writable) {
4407 0           dXSARGS;
4408             const char *path;
4409 0 0         if (items != 1) croak("Usage: file_is_writable($path)");
4410 0           path = SvPV_nolen(ST(0));
4411 0 0         ST(0) = file_is_writable_internal(path) ? &PL_sv_yes : &PL_sv_no;
4412 0           XSRETURN(1);
4413             }
4414              
4415 0           XS_EXTERNAL(XS_file_func_is_executable) {
4416 0           dXSARGS;
4417             const char *path;
4418 0 0         if (items != 1) croak("Usage: file_is_executable($path)");
4419 0           path = SvPV_nolen(ST(0));
4420 0 0         ST(0) = file_is_executable_internal(path) ? &PL_sv_yes : &PL_sv_no;
4421 0           XSRETURN(1);
4422             }
4423              
4424 0           XS_EXTERNAL(XS_file_func_readdir) {
4425 0           dXSARGS;
4426             const char *path;
4427             AV *result;
4428 0 0         if (items != 1) croak("Usage: file_readdir($path)");
4429 0           path = SvPV_nolen(ST(0));
4430 0           result = file_readdir_internal(aTHX_ path);
4431 0           ST(0) = sv_2mortal(newRV_noinc((SV*)result));
4432 0           XSRETURN(1);
4433             }
4434              
4435 0           XS_EXTERNAL(XS_file_func_slurp_raw) {
4436 0           dXSARGS;
4437             const char *path;
4438 0 0         if (items != 1) croak("Usage: file_slurp_raw($path)");
4439 0           path = SvPV_nolen(ST(0));
4440 0           ST(0) = sv_2mortal(file_slurp_raw_internal(aTHX_ path));
4441 0           XSRETURN(1);
4442             }
4443              
4444 0           XS_EXTERNAL(XS_file_func_copy) {
4445 0           dXSARGS;
4446             const char *src;
4447             const char *dst;
4448 0 0         if (items != 2) croak("Usage: file_copy($src, $dst)");
4449 0           src = SvPV_nolen(ST(0));
4450 0           dst = SvPV_nolen(ST(1));
4451 0 0         ST(0) = file_copy_internal(aTHX_ src, dst) ? &PL_sv_yes : &PL_sv_no;
4452 0           XSRETURN(1);
4453             }
4454              
4455 0           XS_EXTERNAL(XS_file_func_move) {
4456 0           dXSARGS;
4457             const char *src;
4458             const char *dst;
4459 0 0         if (items != 2) croak("Usage: file_move($src, $dst)");
4460 0           src = SvPV_nolen(ST(0));
4461 0           dst = SvPV_nolen(ST(1));
4462 0 0         ST(0) = file_move_internal(aTHX_ src, dst) ? &PL_sv_yes : &PL_sv_no;
4463 0           XSRETURN(1);
4464             }
4465              
4466 0           XS_EXTERNAL(XS_file_func_chmod) {
4467 0           dXSARGS;
4468             const char *path;
4469             int mode;
4470 0 0         if (items != 2) croak("Usage: file_chmod($path, $mode)");
4471 0           path = SvPV_nolen(ST(0));
4472 0           mode = SvIV(ST(1));
4473 0 0         ST(0) = file_chmod_internal(path, mode) ? &PL_sv_yes : &PL_sv_no;
4474 0           XSRETURN(1);
4475             }
4476              
4477 0           XS_EXTERNAL(XS_file_func_append) {
4478 0           dXSARGS;
4479             const char *path;
4480 0 0         if (items != 2) croak("Usage: file_append($path, $data)");
4481 0           path = SvPV_nolen(ST(0));
4482 0 0         ST(0) = file_append_internal(aTHX_ path, ST(1)) ? &PL_sv_yes : &PL_sv_no;
4483 0           XSRETURN(1);
4484             }
4485              
4486 0           XS_EXTERNAL(XS_file_func_atomic_spew) {
4487 0           dXSARGS;
4488             const char *path;
4489 0 0         if (items != 2) croak("Usage: file_atomic_spew($path, $data)");
4490 0           path = SvPV_nolen(ST(0));
4491 0 0         ST(0) = file_atomic_spew_internal(aTHX_ path, ST(1)) ? &PL_sv_yes : &PL_sv_no;
4492 0           XSRETURN(1);
4493             }
4494              
4495             /* Function entry for selective import */
4496             typedef struct {
4497             const char *name; /* Name without file_ prefix (e.g., "slurp") */
4498             int args; /* 1 or 2 arguments */
4499             void (*xs_func)(pTHX_ CV*);
4500             Perl_ppaddr_t pp_func;
4501             } ImportEntry;
4502              
4503             static const ImportEntry import_funcs[] = {
4504             /* 1-arg functions */
4505             {"slurp", 1, XS_file_func_slurp, pp_file_slurp},
4506             {"slurp_raw", 1, XS_file_func_slurp_raw, pp_file_slurp_raw},
4507             {"exists", 1, XS_file_func_exists, pp_file_exists},
4508             {"size", 1, XS_file_func_size, pp_file_size},
4509             {"is_file", 1, XS_file_func_is_file, pp_file_is_file},
4510             {"is_dir", 1, XS_file_func_is_dir, pp_file_is_dir},
4511             {"lines", 1, XS_file_func_lines, pp_file_lines},
4512             {"unlink", 1, XS_file_func_unlink, pp_file_unlink},
4513             {"mkdir", 1, XS_file_func_mkdir, pp_file_mkdir},
4514             {"rmdir", 1, XS_file_func_rmdir, pp_file_rmdir},
4515             {"touch", 1, XS_file_func_touch, pp_file_touch},
4516             {"clear_stat_cache", 1, XS_file_func_clear_stat_cache, pp_file_clear_stat_cache},
4517             {"basename", 1, XS_file_func_basename, pp_file_basename},
4518             {"dirname", 1, XS_file_func_dirname, pp_file_dirname},
4519             {"extname", 1, XS_file_func_extname, pp_file_extname},
4520             {"mtime", 1, XS_file_func_mtime, pp_file_mtime},
4521             {"atime", 1, XS_file_func_atime, pp_file_atime},
4522             {"ctime", 1, XS_file_func_ctime, pp_file_ctime},
4523             {"mode", 1, XS_file_func_mode, pp_file_mode},
4524             {"is_link", 1, XS_file_func_is_link, pp_file_is_link},
4525             {"is_readable", 1, XS_file_func_is_readable, pp_file_is_readable},
4526             {"is_writable", 1, XS_file_func_is_writable, pp_file_is_writable},
4527             {"is_executable", 1, XS_file_func_is_executable, pp_file_is_executable},
4528             {"readdir", 1, XS_file_func_readdir, pp_file_readdir},
4529             /* 2-arg functions */
4530             {"spew", 2, XS_file_func_spew, pp_file_spew},
4531             {"copy", 2, XS_file_func_copy, pp_file_copy},
4532             {"move", 2, XS_file_func_move, pp_file_move},
4533             {"chmod", 2, XS_file_func_chmod, pp_file_chmod},
4534             {"append", 2, XS_file_func_append, pp_file_append},
4535             {"atomic_spew", 2, XS_file_func_atomic_spew, pp_file_atomic_spew},
4536             /* variadic functions (args=0 means plain newXS, no custom op) */
4537             {"join", 0, xs_join, NULL},
4538             {"mkpath", 0, xs_mkpath, NULL},
4539             {"rm_rf", 0, xs_rm_rf, NULL},
4540             {NULL, 0, NULL, NULL}
4541             };
4542              
4543             #define IMPORT_FUNCS_COUNT (sizeof(import_funcs) / sizeof(import_funcs[0]) - 1)
4544              
4545 147           static void install_import_entry(pTHX_ const char *pkg, const ImportEntry *e) {
4546             char short_name[256];
4547 147           snprintf(short_name, sizeof(short_name), "file_%s", e->name);
4548 147 100         if (e->args == 0) {
4549             /* Variadic: plain newXS, no custom op */
4550             char full_name[256];
4551 15           snprintf(full_name, sizeof(full_name), "%s::%s", pkg, short_name);
4552 15           newXS(full_name, e->xs_func, __FILE__);
4553 132 100         } else if (e->args == 1) {
4554 103           install_file_func_1arg(aTHX_ pkg, short_name, e->xs_func, e->pp_func);
4555             } else {
4556 29           install_file_func_2arg(aTHX_ pkg, short_name, e->xs_func, e->pp_func);
4557             }
4558 147           }
4559              
4560 4           static void install_all_imports(pTHX_ const char *pkg) {
4561             int i;
4562 136 100         for (i = 0; import_funcs[i].name != NULL; i++) {
4563 132           install_import_entry(aTHX_ pkg, &import_funcs[i]);
4564             }
4565 4           }
4566              
4567             /* file::import - import function-style accessors with custom ops */
4568 25           XS_EXTERNAL(XS_file_import) {
4569 25           dXSARGS;
4570             const char *pkg;
4571             int i, j;
4572              
4573             /* Get caller's package */
4574 25 50         pkg = CopSTASHPV(PL_curcop);
    50          
    50          
    50          
    0          
    50          
    50          
4575              
4576             /* No args after package name = no imports */
4577 25 100         if (items <= 1) {
4578 17           XSRETURN_EMPTY;
4579             }
4580              
4581             /* Process each requested import */
4582 23 100         for (i = 1; i < items; i++) {
4583             STRLEN len;
4584 19           const char *arg = SvPV(ST(i), len);
4585              
4586             /* Check for :all or import (both mean import everything) */
4587 19 100         if ((len == 4 && strEQ(arg, ":all")) ||
    100          
4588 17 100         (len == 6 && strEQ(arg, "import"))) {
    100          
4589 4           install_all_imports(aTHX_ pkg);
4590 4           XSRETURN_EMPTY; /* :all means we're done */
4591             }
4592              
4593             /* Look up the requested function */
4594 290 50         for (j = 0; import_funcs[j].name != NULL; j++) {
4595 290 100         if (strEQ(arg, import_funcs[j].name)) {
4596 15           install_import_entry(aTHX_ pkg, &import_funcs[j]);
4597 15           break;
4598             }
4599             }
4600              
4601             /* If not found, warn but don't die */
4602 15 50         if (import_funcs[j].name == NULL) {
4603 0           warn("File::Raw: '%s' is not exported", arg);
4604             }
4605             }
4606              
4607 4           XSRETURN_EMPTY;
4608             }
4609              
4610             /* ============================================
4611             Boot
4612             ============================================ */
4613              
4614 18           XS_EXTERNAL(boot_File__Raw) {
4615 18           dXSBOOTARGSXSAPIVERCHK;
4616             PERL_UNUSED_VAR(items);
4617              
4618 18           file_init(aTHX);
4619              
4620             /* Register custom ops */
4621 18           XopENTRY_set(&file_slurp_xop, xop_name, "file_slurp");
4622 18           XopENTRY_set(&file_slurp_xop, xop_desc, "file slurp");
4623 18           XopENTRY_set(&file_slurp_xop, xop_class, OA_UNOP);
4624 18           Perl_custom_op_register(aTHX_ pp_file_slurp, &file_slurp_xop);
4625              
4626 18           XopENTRY_set(&file_spew_xop, xop_name, "file_spew");
4627 18           XopENTRY_set(&file_spew_xop, xop_desc, "file spew");
4628 18           XopENTRY_set(&file_spew_xop, xop_class, OA_BINOP);
4629 18           Perl_custom_op_register(aTHX_ pp_file_spew, &file_spew_xop);
4630              
4631 18           XopENTRY_set(&file_exists_xop, xop_name, "file_exists");
4632 18           XopENTRY_set(&file_exists_xop, xop_desc, "file exists");
4633 18           XopENTRY_set(&file_exists_xop, xop_class, OA_UNOP);
4634 18           Perl_custom_op_register(aTHX_ pp_file_exists, &file_exists_xop);
4635              
4636 18           XopENTRY_set(&file_size_xop, xop_name, "file_size");
4637 18           XopENTRY_set(&file_size_xop, xop_desc, "file size");
4638 18           XopENTRY_set(&file_size_xop, xop_class, OA_UNOP);
4639 18           Perl_custom_op_register(aTHX_ pp_file_size, &file_size_xop);
4640              
4641 18           XopENTRY_set(&file_is_file_xop, xop_name, "file_is_file");
4642 18           XopENTRY_set(&file_is_file_xop, xop_desc, "file is_file");
4643 18           XopENTRY_set(&file_is_file_xop, xop_class, OA_UNOP);
4644 18           Perl_custom_op_register(aTHX_ pp_file_is_file, &file_is_file_xop);
4645              
4646 18           XopENTRY_set(&file_is_dir_xop, xop_name, "file_is_dir");
4647 18           XopENTRY_set(&file_is_dir_xop, xop_desc, "file is_dir");
4648 18           XopENTRY_set(&file_is_dir_xop, xop_class, OA_UNOP);
4649 18           Perl_custom_op_register(aTHX_ pp_file_is_dir, &file_is_dir_xop);
4650              
4651 18           XopENTRY_set(&file_lines_xop, xop_name, "file_lines");
4652 18           XopENTRY_set(&file_lines_xop, xop_desc, "file lines");
4653 18           XopENTRY_set(&file_lines_xop, xop_class, OA_UNOP);
4654 18           Perl_custom_op_register(aTHX_ pp_file_lines, &file_lines_xop);
4655              
4656 18           XopENTRY_set(&file_unlink_xop, xop_name, "file_unlink");
4657 18           XopENTRY_set(&file_unlink_xop, xop_desc, "file unlink");
4658 18           XopENTRY_set(&file_unlink_xop, xop_class, OA_UNOP);
4659 18           Perl_custom_op_register(aTHX_ pp_file_unlink, &file_unlink_xop);
4660              
4661 18           XopENTRY_set(&file_mkdir_xop, xop_name, "file_mkdir");
4662 18           XopENTRY_set(&file_mkdir_xop, xop_desc, "file mkdir");
4663 18           XopENTRY_set(&file_mkdir_xop, xop_class, OA_UNOP);
4664 18           Perl_custom_op_register(aTHX_ pp_file_mkdir, &file_mkdir_xop);
4665              
4666 18           XopENTRY_set(&file_rmdir_xop, xop_name, "file_rmdir");
4667 18           XopENTRY_set(&file_rmdir_xop, xop_desc, "file rmdir");
4668 18           XopENTRY_set(&file_rmdir_xop, xop_class, OA_UNOP);
4669 18           Perl_custom_op_register(aTHX_ pp_file_rmdir, &file_rmdir_xop);
4670              
4671 18           XopENTRY_set(&file_touch_xop, xop_name, "file_touch");
4672 18           XopENTRY_set(&file_touch_xop, xop_desc, "file touch");
4673 18           XopENTRY_set(&file_touch_xop, xop_class, OA_UNOP);
4674 18           Perl_custom_op_register(aTHX_ pp_file_touch, &file_touch_xop);
4675              
4676 18           XopENTRY_set(&file_clear_stat_cache_xop, xop_name, "file_clear_stat_cache");
4677 18           XopENTRY_set(&file_clear_stat_cache_xop, xop_desc, "clear stat cache");
4678 18           XopENTRY_set(&file_clear_stat_cache_xop, xop_class, OA_BASEOP);
4679 18           Perl_custom_op_register(aTHX_ pp_file_clear_stat_cache, &file_clear_stat_cache_xop);
4680              
4681 18           XopENTRY_set(&file_basename_xop, xop_name, "file_basename");
4682 18           XopENTRY_set(&file_basename_xop, xop_desc, "file basename");
4683 18           XopENTRY_set(&file_basename_xop, xop_class, OA_UNOP);
4684 18           Perl_custom_op_register(aTHX_ pp_file_basename, &file_basename_xop);
4685              
4686 18           XopENTRY_set(&file_dirname_xop, xop_name, "file_dirname");
4687 18           XopENTRY_set(&file_dirname_xop, xop_desc, "file dirname");
4688 18           XopENTRY_set(&file_dirname_xop, xop_class, OA_UNOP);
4689 18           Perl_custom_op_register(aTHX_ pp_file_dirname, &file_dirname_xop);
4690              
4691 18           XopENTRY_set(&file_extname_xop, xop_name, "file_extname");
4692 18           XopENTRY_set(&file_extname_xop, xop_desc, "file extname");
4693 18           XopENTRY_set(&file_extname_xop, xop_class, OA_UNOP);
4694 18           Perl_custom_op_register(aTHX_ pp_file_extname, &file_extname_xop);
4695              
4696 18           XopENTRY_set(&file_mtime_xop, xop_name, "file_mtime");
4697 18           XopENTRY_set(&file_mtime_xop, xop_desc, "file mtime");
4698 18           XopENTRY_set(&file_mtime_xop, xop_class, OA_UNOP);
4699 18           Perl_custom_op_register(aTHX_ pp_file_mtime, &file_mtime_xop);
4700              
4701 18           XopENTRY_set(&file_atime_xop, xop_name, "file_atime");
4702 18           XopENTRY_set(&file_atime_xop, xop_desc, "file atime");
4703 18           XopENTRY_set(&file_atime_xop, xop_class, OA_UNOP);
4704 18           Perl_custom_op_register(aTHX_ pp_file_atime, &file_atime_xop);
4705              
4706 18           XopENTRY_set(&file_ctime_xop, xop_name, "file_ctime");
4707 18           XopENTRY_set(&file_ctime_xop, xop_desc, "file ctime");
4708 18           XopENTRY_set(&file_ctime_xop, xop_class, OA_UNOP);
4709 18           Perl_custom_op_register(aTHX_ pp_file_ctime, &file_ctime_xop);
4710              
4711 18           XopENTRY_set(&file_mode_xop, xop_name, "file_mode");
4712 18           XopENTRY_set(&file_mode_xop, xop_desc, "file mode");
4713 18           XopENTRY_set(&file_mode_xop, xop_class, OA_UNOP);
4714 18           Perl_custom_op_register(aTHX_ pp_file_mode, &file_mode_xop);
4715              
4716 18           XopENTRY_set(&file_is_link_xop, xop_name, "file_is_link");
4717 18           XopENTRY_set(&file_is_link_xop, xop_desc, "file is_link");
4718 18           XopENTRY_set(&file_is_link_xop, xop_class, OA_UNOP);
4719 18           Perl_custom_op_register(aTHX_ pp_file_is_link, &file_is_link_xop);
4720              
4721 18           XopENTRY_set(&file_is_readable_xop, xop_name, "file_is_readable");
4722 18           XopENTRY_set(&file_is_readable_xop, xop_desc, "file is_readable");
4723 18           XopENTRY_set(&file_is_readable_xop, xop_class, OA_UNOP);
4724 18           Perl_custom_op_register(aTHX_ pp_file_is_readable, &file_is_readable_xop);
4725              
4726 18           XopENTRY_set(&file_is_writable_xop, xop_name, "file_is_writable");
4727 18           XopENTRY_set(&file_is_writable_xop, xop_desc, "file is_writable");
4728 18           XopENTRY_set(&file_is_writable_xop, xop_class, OA_UNOP);
4729 18           Perl_custom_op_register(aTHX_ pp_file_is_writable, &file_is_writable_xop);
4730              
4731 18           XopENTRY_set(&file_is_executable_xop, xop_name, "file_is_executable");
4732 18           XopENTRY_set(&file_is_executable_xop, xop_desc, "file is_executable");
4733 18           XopENTRY_set(&file_is_executable_xop, xop_class, OA_UNOP);
4734 18           Perl_custom_op_register(aTHX_ pp_file_is_executable, &file_is_executable_xop);
4735              
4736 18           XopENTRY_set(&file_readdir_xop, xop_name, "file_readdir");
4737 18           XopENTRY_set(&file_readdir_xop, xop_desc, "file readdir");
4738 18           XopENTRY_set(&file_readdir_xop, xop_class, OA_UNOP);
4739 18           Perl_custom_op_register(aTHX_ pp_file_readdir, &file_readdir_xop);
4740              
4741 18           XopENTRY_set(&file_slurp_raw_xop, xop_name, "file_slurp_raw");
4742 18           XopENTRY_set(&file_slurp_raw_xop, xop_desc, "file slurp_raw");
4743 18           XopENTRY_set(&file_slurp_raw_xop, xop_class, OA_UNOP);
4744 18           Perl_custom_op_register(aTHX_ pp_file_slurp_raw, &file_slurp_raw_xop);
4745              
4746 18           XopENTRY_set(&file_copy_xop, xop_name, "file_copy");
4747 18           XopENTRY_set(&file_copy_xop, xop_desc, "file copy");
4748 18           XopENTRY_set(&file_copy_xop, xop_class, OA_BINOP);
4749 18           Perl_custom_op_register(aTHX_ pp_file_copy, &file_copy_xop);
4750              
4751 18           XopENTRY_set(&file_move_xop, xop_name, "file_move");
4752 18           XopENTRY_set(&file_move_xop, xop_desc, "file move");
4753 18           XopENTRY_set(&file_move_xop, xop_class, OA_BINOP);
4754 18           Perl_custom_op_register(aTHX_ pp_file_move, &file_move_xop);
4755              
4756 18           XopENTRY_set(&file_chmod_xop, xop_name, "file_chmod");
4757 18           XopENTRY_set(&file_chmod_xop, xop_desc, "file chmod");
4758 18           XopENTRY_set(&file_chmod_xop, xop_class, OA_BINOP);
4759 18           Perl_custom_op_register(aTHX_ pp_file_chmod, &file_chmod_xop);
4760              
4761 18           XopENTRY_set(&file_append_xop, xop_name, "file_append");
4762 18           XopENTRY_set(&file_append_xop, xop_desc, "file append");
4763 18           XopENTRY_set(&file_append_xop, xop_class, OA_BINOP);
4764 18           Perl_custom_op_register(aTHX_ pp_file_append, &file_append_xop);
4765              
4766 18           XopENTRY_set(&file_atomic_spew_xop, xop_name, "file_atomic_spew");
4767 18           XopENTRY_set(&file_atomic_spew_xop, xop_desc, "file atomic_spew");
4768 18           XopENTRY_set(&file_atomic_spew_xop, xop_class, OA_BINOP);
4769 18           Perl_custom_op_register(aTHX_ pp_file_atomic_spew, &file_atomic_spew_xop);
4770              
4771             /* Install functions with call checker for custom op optimization */
4772             {
4773             CV *cv;
4774             SV *ckobj;
4775              
4776             /* 1-arg functions with call checker */
4777 18           cv = newXS("File::Raw::size", xs_size, __FILE__);
4778 18           ckobj = newSViv(PTR2IV(pp_file_size));
4779 18           cv_set_call_checker(cv, file_call_checker_1arg, ckobj);
4780              
4781 18           cv = newXS("File::Raw::mtime", xs_mtime, __FILE__);
4782 18           ckobj = newSViv(PTR2IV(pp_file_mtime));
4783 18           cv_set_call_checker(cv, file_call_checker_1arg, ckobj);
4784              
4785 18           cv = newXS("File::Raw::atime", xs_atime, __FILE__);
4786 18           ckobj = newSViv(PTR2IV(pp_file_atime));
4787 18           cv_set_call_checker(cv, file_call_checker_1arg, ckobj);
4788              
4789 18           cv = newXS("File::Raw::ctime", xs_ctime, __FILE__);
4790 18           ckobj = newSViv(PTR2IV(pp_file_ctime));
4791 18           cv_set_call_checker(cv, file_call_checker_1arg, ckobj);
4792              
4793 18           cv = newXS("File::Raw::mode", xs_mode, __FILE__);
4794 18           ckobj = newSViv(PTR2IV(pp_file_mode));
4795 18           cv_set_call_checker(cv, file_call_checker_1arg, ckobj);
4796              
4797 18           cv = newXS("File::Raw::exists", xs_exists, __FILE__);
4798 18           ckobj = newSViv(PTR2IV(pp_file_exists));
4799 18           cv_set_call_checker(cv, file_call_checker_1arg, ckobj);
4800              
4801 18           cv = newXS("File::Raw::is_file", xs_is_file, __FILE__);
4802 18           ckobj = newSViv(PTR2IV(pp_file_is_file));
4803 18           cv_set_call_checker(cv, file_call_checker_1arg, ckobj);
4804              
4805 18           cv = newXS("File::Raw::is_dir", xs_is_dir, __FILE__);
4806 18           ckobj = newSViv(PTR2IV(pp_file_is_dir));
4807 18           cv_set_call_checker(cv, file_call_checker_1arg, ckobj);
4808              
4809 18           cv = newXS("File::Raw::is_link", xs_is_link, __FILE__);
4810 18           ckobj = newSViv(PTR2IV(pp_file_is_link));
4811 18           cv_set_call_checker(cv, file_call_checker_1arg, ckobj);
4812              
4813 18           cv = newXS("File::Raw::is_readable", xs_is_readable, __FILE__);
4814 18           ckobj = newSViv(PTR2IV(pp_file_is_readable));
4815 18           cv_set_call_checker(cv, file_call_checker_1arg, ckobj);
4816              
4817 18           cv = newXS("File::Raw::is_writable", xs_is_writable, __FILE__);
4818 18           ckobj = newSViv(PTR2IV(pp_file_is_writable));
4819 18           cv_set_call_checker(cv, file_call_checker_1arg, ckobj);
4820              
4821 18           cv = newXS("File::Raw::is_executable", xs_is_executable, __FILE__);
4822 18           ckobj = newSViv(PTR2IV(pp_file_is_executable));
4823 18           cv_set_call_checker(cv, file_call_checker_1arg, ckobj);
4824              
4825             /* File manipulation - 1-arg */
4826 18           cv = newXS("File::Raw::unlink", xs_unlink, __FILE__);
4827 18           ckobj = newSViv(PTR2IV(pp_file_unlink));
4828 18           cv_set_call_checker(cv, file_call_checker_1arg, ckobj);
4829              
4830 18           cv = newXS("File::Raw::mkdir", xs_mkdir, __FILE__);
4831 18           ckobj = newSViv(PTR2IV(pp_file_mkdir));
4832 18           cv_set_call_checker(cv, file_call_checker_1arg, ckobj);
4833              
4834 18           cv = newXS("File::Raw::rmdir", xs_rmdir, __FILE__);
4835 18           ckobj = newSViv(PTR2IV(pp_file_rmdir));
4836 18           cv_set_call_checker(cv, file_call_checker_1arg, ckobj);
4837              
4838 18           cv = newXS("File::Raw::touch", xs_touch, __FILE__);
4839 18           ckobj = newSViv(PTR2IV(pp_file_touch));
4840 18           cv_set_call_checker(cv, file_call_checker_1arg, ckobj);
4841              
4842 18           cv = newXS("File::Raw::clear_stat_cache", xs_clear_stat_cache, __FILE__);
4843 18           ckobj = newSViv(PTR2IV(pp_file_clear_stat_cache));
4844 18           cv_set_call_checker(cv, file_call_checker_1arg, ckobj);
4845              
4846 18           cv = newXS("File::Raw::basename", xs_basename, __FILE__);
4847 18           ckobj = newSViv(PTR2IV(pp_file_basename));
4848 18           cv_set_call_checker(cv, file_call_checker_1arg, ckobj);
4849              
4850 18           cv = newXS("File::Raw::dirname", xs_dirname, __FILE__);
4851 18           ckobj = newSViv(PTR2IV(pp_file_dirname));
4852 18           cv_set_call_checker(cv, file_call_checker_1arg, ckobj);
4853              
4854 18           cv = newXS("File::Raw::extname", xs_extname, __FILE__);
4855 18           ckobj = newSViv(PTR2IV(pp_file_extname));
4856 18           cv_set_call_checker(cv, file_call_checker_1arg, ckobj);
4857              
4858 18           cv = newXS("File::Raw::slurp", xs_slurp, __FILE__);
4859 18           ckobj = newSViv(PTR2IV(pp_file_slurp));
4860 18           cv_set_call_checker(cv, file_call_checker_1arg, ckobj);
4861              
4862 18           cv = newXS("File::Raw::slurp_raw", xs_slurp_raw, __FILE__);
4863 18           ckobj = newSViv(PTR2IV(pp_file_slurp_raw));
4864 18           cv_set_call_checker(cv, file_call_checker_1arg, ckobj);
4865              
4866 18           cv = newXS("File::Raw::lines", xs_lines, __FILE__);
4867 18           ckobj = newSViv(PTR2IV(pp_file_lines));
4868 18           cv_set_call_checker(cv, file_call_checker_1arg, ckobj);
4869              
4870 18           cv = newXS("File::Raw::readdir", xs_readdir, __FILE__);
4871 18           ckobj = newSViv(PTR2IV(pp_file_readdir));
4872 18           cv_set_call_checker(cv, file_call_checker_1arg, ckobj);
4873              
4874             /* 2-arg functions with call checker */
4875 18           cv = newXS("File::Raw::spew", xs_spew, __FILE__);
4876 18           ckobj = newSViv(PTR2IV(pp_file_spew));
4877 18           cv_set_call_checker(cv, file_call_checker_2arg, ckobj);
4878              
4879 18           cv = newXS("File::Raw::append", xs_append, __FILE__);
4880 18           ckobj = newSViv(PTR2IV(pp_file_append));
4881 18           cv_set_call_checker(cv, file_call_checker_2arg, ckobj);
4882              
4883 18           cv = newXS("File::Raw::copy", xs_copy, __FILE__);
4884 18           ckobj = newSViv(PTR2IV(pp_file_copy));
4885 18           cv_set_call_checker(cv, file_call_checker_2arg, ckobj);
4886              
4887 18           cv = newXS("File::Raw::move", xs_move, __FILE__);
4888 18           ckobj = newSViv(PTR2IV(pp_file_move));
4889 18           cv_set_call_checker(cv, file_call_checker_2arg, ckobj);
4890              
4891 18           cv = newXS("File::Raw::chmod", xs_chmod, __FILE__);
4892 18           ckobj = newSViv(PTR2IV(pp_file_chmod));
4893 18           cv_set_call_checker(cv, file_call_checker_2arg, ckobj);
4894              
4895 18           cv = newXS("File::Raw::atomic_spew", xs_atomic_spew, __FILE__);
4896 18           ckobj = newSViv(PTR2IV(pp_file_atomic_spew));
4897 18           cv_set_call_checker(cv, file_call_checker_2arg, ckobj);
4898             }
4899              
4900             /* Functions without custom op optimization */
4901 18           newXS("File::Raw::join", xs_join, __FILE__);
4902 18           newXS("File::Raw::mkpath", xs_mkpath, __FILE__);
4903 18           newXS("File::Raw::rm_rf", xs_rm_rf, __FILE__);
4904 18           newXS("File::Raw::each_line", xs_each_line, __FILE__);
4905 18           newXS("File::Raw::grep_lines", xs_grep_lines, __FILE__);
4906 18           newXS("File::Raw::count_lines", xs_count_lines, __FILE__);
4907 18           newXS("File::Raw::find_line", xs_find_line, __FILE__);
4908 18           newXS("File::Raw::map_lines", xs_map_lines, __FILE__);
4909 18           newXS("File::Raw::register_line_callback", xs_register_line_callback, __FILE__);
4910 18           newXS("File::Raw::list_line_callbacks", xs_list_line_callbacks, __FILE__);
4911              
4912             /* File hooks */
4913 18           newXS("File::Raw::register_read_hook", xs_register_read_hook, __FILE__);
4914 18           newXS("File::Raw::register_write_hook", xs_register_write_hook, __FILE__);
4915 18           newXS("File::Raw::clear_hooks", xs_clear_hooks, __FILE__);
4916 18           newXS("File::Raw::has_hooks", xs_has_hooks, __FILE__);
4917              
4918             /* Combined stat - all attributes in one syscall */
4919 18           newXS("File::Raw::stat", xs_stat_all, __FILE__);
4920              
4921             /* Head and tail */
4922 18           newXS("File::Raw::head", xs_head, __FILE__);
4923 18           newXS("File::Raw::tail", xs_tail, __FILE__);
4924              
4925             /* Import function */
4926 18           newXS("File::Raw::import", XS_file_import, __FILE__);
4927              
4928             /* Memory-mapped files */
4929 18           newXS("File::Raw::mmap_open", xs_mmap_open, __FILE__);
4930 18           newXS("File::Raw::mmap::data", xs_mmap_data, __FILE__);
4931 18           newXS("File::Raw::mmap::sync", xs_mmap_sync, __FILE__);
4932 18           newXS("File::Raw::mmap::close", xs_mmap_close, __FILE__);
4933 18           newXS("File::Raw::mmap::DESTROY", xs_mmap_DESTROY, __FILE__);
4934              
4935             /* Line iterators */
4936 18           newXS("File::Raw::lines_iter", xs_lines_iter, __FILE__);
4937 18           newXS("File::Raw::lines::next", xs_lines_iter_next, __FILE__);
4938 18           newXS("File::Raw::lines::eof", xs_lines_iter_eof, __FILE__);
4939 18           newXS("File::Raw::lines::close", xs_lines_iter_close, __FILE__);
4940 18           newXS("File::Raw::lines::DESTROY", xs_lines_iter_DESTROY, __FILE__);
4941              
4942             /* Register cleanup for global destruction */
4943 18           Perl_call_atexit(aTHX_ file_cleanup_callback_registry, NULL);
4944              
4945             #if PERL_REVISION > 5 || (PERL_REVISION == 5 && PERL_VERSION >= 22)
4946 18           Perl_xs_boot_epilog(aTHX_ ax);
4947             #else
4948             XSRETURN_YES;
4949             #endif
4950 18           }