File Coverage

file.c
Criterion Covered Total %
statement 1946 2544 76.4
branch 763 1394 54.7
condition n/a
subroutine n/a
pod n/a
total 2709 3938 68.7


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