| 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 */ |