File Coverage

include/file_hooks_impl.h
Criterion Covered Total %
statement 56 98 57.1
branch 24 62 38.7
condition n/a
subroutine n/a
pod n/a
total 80 160 50.0


line stmt bran cond sub pod time code
1             /*
2             * file_hooks_impl.h - Hook system implementation
3             *
4             * Header-only implementation of the C-API declared in file_hooks.h.
5             * Inclusion model: a single .c file in each linkable image (Raw.so, plus
6             * any test extension that wants to call the hook API directly) #includes
7             * this once. The state lives as file-scope statics in that translation
8             * unit.
9             *
10             * Why a header rather than a separate .c shipped alongside Raw:
11             * - Unix builds load Raw.so with RTLD_GLOBAL (and macOS uses flat
12             * namespace), so the *function* symbols dedupe at load time and
13             * external XS extensions transparently mutate Raw.so's hook state.
14             * - Windows/Cygwin can't dedupe across DLLs without an import library
15             * for Raw.dll. Including this header inside the test extension lets
16             * it link cleanly; cross-DLL state sharing is then unavailable on
17             * those platforms (the test suite skips the affected subtests).
18             */
19              
20             #ifndef FILE_HOOKS_IMPL_H
21             #define FILE_HOOKS_IMPL_H
22              
23             #include "file_hooks.h"
24              
25             /* Global hook pointers - NULL when no hooks registered (fast check) */
26             static file_hook_func g_file_read_hook = NULL;
27             static void *g_file_read_hook_data = NULL;
28             static file_hook_func g_file_write_hook = NULL;
29             static void *g_file_write_hook_data = NULL;
30              
31             /* Hook linked lists for multiple hooks per phase */
32             static FileHookEntry *g_file_hooks[4] = { NULL, NULL, NULL, NULL };
33              
34 10           void file_set_read_hook(pTHX_ file_hook_func func, void *user_data) {
35             PERL_UNUSED_CONTEXT;
36 10           g_file_read_hook = func;
37 10           g_file_read_hook_data = user_data;
38 10           }
39              
40 6           void file_set_write_hook(pTHX_ file_hook_func func, void *user_data) {
41             PERL_UNUSED_CONTEXT;
42 6           g_file_write_hook = func;
43 6           g_file_write_hook_data = user_data;
44 6           }
45              
46 9           file_hook_func file_get_read_hook(void) {
47 9           return g_file_read_hook;
48             }
49              
50 3           file_hook_func file_get_write_hook(void) {
51 3           return g_file_write_hook;
52             }
53              
54 0           int file_has_hooks(FileHookPhase phase) {
55             /* Fast path for simple hooks */
56 0 0         if (phase == FILE_HOOK_PHASE_READ && g_file_read_hook) return 1;
    0          
57 0 0         if (phase == FILE_HOOK_PHASE_WRITE && g_file_write_hook) return 1;
    0          
58             /* Check hook list */
59 0           return g_file_hooks[phase] != NULL;
60             }
61              
62 0           int file_register_hook_c(pTHX_ FileHookPhase phase, const char *name,
63             file_hook_func func, int priority, void *user_data) {
64             FileHookEntry *entry, *prev, *curr;
65              
66 0 0         if (phase > FILE_HOOK_PHASE_CLOSE) return 0;
67              
68             /* Allocate new entry */
69 0           Newxz(entry, 1, FileHookEntry);
70 0           entry->name = name; /* Caller owns the string */
71 0           entry->c_func = func;
72 0           entry->perl_callback = NULL;
73 0           entry->priority = priority;
74 0           entry->user_data = user_data;
75 0           entry->next = NULL;
76              
77             /* Insert in priority order */
78 0           prev = NULL;
79 0           curr = g_file_hooks[phase];
80 0 0         while (curr && curr->priority <= priority) {
    0          
81 0           prev = curr;
82 0           curr = curr->next;
83             }
84              
85 0 0         if (prev) {
86 0           entry->next = prev->next;
87 0           prev->next = entry;
88             } else {
89 0           entry->next = g_file_hooks[phase];
90 0           g_file_hooks[phase] = entry;
91             }
92              
93 0           return 1;
94             }
95              
96 0           int file_unregister_hook(pTHX_ FileHookPhase phase, const char *name) {
97             FileHookEntry *prev, *curr;
98             PERL_UNUSED_CONTEXT;
99              
100 0 0         if (phase > FILE_HOOK_PHASE_CLOSE) return 0;
101              
102 0           prev = NULL;
103 0           curr = g_file_hooks[phase];
104 0 0         while (curr) {
105 0 0         if (strcmp(curr->name, name) == 0) {
106 0 0         if (prev) {
107 0           prev->next = curr->next;
108             } else {
109 0           g_file_hooks[phase] = curr->next;
110             }
111 0 0         if (curr->perl_callback) {
112 0           SvREFCNT_dec(curr->perl_callback);
113             }
114 0           Safefree(curr);
115 0           return 1;
116             }
117 0           prev = curr;
118 0           curr = curr->next;
119             }
120 0           return 0;
121             }
122              
123 14           SV* file_run_hooks(pTHX_ FileHookPhase phase, const char *path, SV *data) {
124             FileHookContext ctx;
125             FileHookEntry *entry;
126 14           SV *result = data;
127 14           file_hook_func simple_hook = NULL;
128 14           void *simple_data = NULL;
129              
130             /* Check simple hooks first */
131 14 100         if (phase == FILE_HOOK_PHASE_READ && g_file_read_hook) {
    100          
132 2           simple_hook = g_file_read_hook;
133 2           simple_data = g_file_read_hook_data;
134 12 100         } else if (phase == FILE_HOOK_PHASE_WRITE && g_file_write_hook) {
    100          
135 2           simple_hook = g_file_write_hook;
136 2           simple_data = g_file_write_hook_data;
137             }
138              
139             /* Run simple hook if present */
140 14 100         if (simple_hook) {
141 4           ctx.path = path;
142 4           ctx.data = result;
143 4           ctx.phase = phase;
144 4           ctx.user_data = simple_data;
145 4           ctx.cancel = 0;
146              
147 4           result = simple_hook(aTHX_ &ctx);
148 4 50         if (!result || ctx.cancel) return NULL;
    50          
149             }
150              
151             /* Run hook chain */
152 24 100         for (entry = g_file_hooks[phase]; entry; entry = entry->next) {
153 10           ctx.path = path;
154 10           ctx.data = result;
155 10           ctx.phase = phase;
156 10           ctx.user_data = entry->user_data;
157 10           ctx.cancel = 0;
158              
159 10 50         if (entry->c_func) {
160 0           result = entry->c_func(aTHX_ &ctx);
161 10 50         } else if (entry->perl_callback) {
162             /* Call Perl callback */
163 10           dSP;
164             int count;
165              
166 10           ENTER;
167 10           SAVETMPS;
168 10 50         PUSHMARK(SP);
169 10 50         mXPUSHs(newSVpv(path, 0));
170 10 50         mXPUSHs(SvREFCNT_inc(result));
171 10           PUTBACK;
172              
173 10           count = call_sv(entry->perl_callback, G_SCALAR);
174              
175 10           SPAGAIN;
176 10 50         if (count > 0) {
177 10           SV *ret = POPs;
178 10 50         if (SvOK(ret)) {
179 10           result = newSVsv(ret);
180             } else {
181 0           ctx.cancel = 1;
182             }
183             }
184 10           PUTBACK;
185 10 50         FREETMPS;
186 10           LEAVE;
187             }
188              
189 10 50         if (!result || ctx.cancel) return NULL;
    50          
190             }
191              
192 14           return result;
193             }
194              
195             #endif /* FILE_HOOKS_IMPL_H */