File Coverage

file.c
Criterion Covered Total %
statement 1893 2488 76.0
branch 739 1356 54.5
condition n/a
subroutine n/a
pod n/a
total 2632 3844 68.4


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