/src/ghostpdl/psi/zfproc.c
Line | Count | Source |
1 | | /* Copyright (C) 2001-2026 Artifex Software, Inc. |
2 | | All Rights Reserved. |
3 | | |
4 | | This software is provided AS-IS with no warranty, either express or |
5 | | implied. |
6 | | |
7 | | This software is distributed under license and may not be copied, |
8 | | modified or distributed except as expressly authorized under the terms |
9 | | of the license contained in the file LICENSE in this distribution. |
10 | | |
11 | | Refer to licensing information at http://www.artifex.com or contact |
12 | | Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco, |
13 | | CA 94129, USA, for further information. |
14 | | */ |
15 | | |
16 | | |
17 | | /* Procedure-based filter stream support */ |
18 | | #include "memory_.h" |
19 | | #include "stat_.h" /* get system header early to avoid name clash on Cygwin */ |
20 | | #include "ghost.h" |
21 | | #include "oper.h" /* for ifilter.h */ |
22 | | #include "estack.h" |
23 | | #include "gsstruct.h" |
24 | | #include "ialloc.h" |
25 | | #include "istruct.h" /* for RELOC_REF_VAR */ |
26 | | #include "stream.h" |
27 | | #include "strimpl.h" |
28 | | #include "ifilter.h" |
29 | | #include "files.h" |
30 | | #include "store.h" |
31 | | |
32 | | #include "isave.h" |
33 | | #include "isstate.h" |
34 | | /* ---------------- Generic ---------------- */ |
35 | | |
36 | | /* GC procedures */ |
37 | | static |
38 | | CLEAR_MARKS_PROC(sproc_clear_marks) |
39 | 756k | { |
40 | 756k | stream_proc_state *const pptr = vptr; |
41 | | |
42 | 756k | r_clear_attrs(&pptr->proc, l_mark); |
43 | 756k | r_clear_attrs(&pptr->data, l_mark); |
44 | 756k | } |
45 | | static |
46 | 68.3k | ENUM_PTRS_WITH(sproc_enum_ptrs, stream_proc_state *pptr) return 0; |
47 | 22.7k | case 0: |
48 | 22.7k | ENUM_RETURN_REF(&pptr->proc); |
49 | 22.7k | case 1: |
50 | 22.7k | ENUM_RETURN_REF(&pptr->data); |
51 | 68.3k | ENUM_PTRS_END |
52 | 22.7k | static RELOC_PTRS_WITH(sproc_reloc_ptrs, stream_proc_state *pptr); |
53 | 22.7k | RELOC_REF_VAR(pptr->proc); |
54 | 22.7k | r_clear_attrs(&pptr->proc, l_mark); |
55 | 22.7k | RELOC_REF_VAR(pptr->data); |
56 | 22.7k | r_clear_attrs(&pptr->data, l_mark); |
57 | 22.7k | RELOC_PTRS_END |
58 | | |
59 | | /* Structure type for procedure-based streams. */ |
60 | | private_st_stream_proc_state(); |
61 | | |
62 | | /* Allocate and open a procedure-based filter. */ |
63 | | /* The caller must have checked that *sop is a procedure. */ |
64 | | static int |
65 | | s_proc_init(ref * sop, stream ** psstrm, uint mode, |
66 | | const stream_template * temp, const stream_procs * procs, |
67 | | gs_ref_memory_t *imem) |
68 | 1.03M | { |
69 | 1.03M | gs_memory_t *const mem = (gs_memory_t *)imem; |
70 | 1.03M | stream *sstrm = file_alloc_stream(mem, "s_proc_init(stream)"); |
71 | 1.03M | stream_proc_state *state = (stream_proc_state *) |
72 | 1.03M | s_alloc_state(mem, &st_sproc_state, "s_proc_init(state)"); |
73 | | |
74 | 1.03M | if (sstrm == 0 || state == 0) { |
75 | 0 | gs_free_object(mem, state, "s_proc_init(state)"); |
76 | | /*gs_free_object(mem, sstrm, "s_proc_init(stream)"); *//* just leave it on the file list */ |
77 | 0 | return_error(gs_error_VMerror); |
78 | 0 | } |
79 | 1.03M | s_std_init(sstrm, NULL, 0, procs, mode); |
80 | 1.03M | sstrm->procs.process = temp->process; |
81 | 1.03M | state->templat = temp; |
82 | 1.03M | state->memory = mem; |
83 | 1.03M | state->eof = 0; |
84 | 1.03M | state->proc = *sop; |
85 | 1.03M | make_empty_string(&state->data, a_all); |
86 | 1.03M | state->data_memory = NULL; |
87 | 1.03M | state->data_save_id = 0; |
88 | 1.03M | state->index = 0; |
89 | 1.03M | sstrm->state = (stream_state *) state; |
90 | 1.03M | *psstrm = sstrm; |
91 | 1.03M | return 0; |
92 | 1.03M | } |
93 | | |
94 | | /* |
95 | | * Note where the data string just stored in ss->data was allocated, and |
96 | | * which save was innermost at that time (if the space the string was created |
97 | | * in was local VM). The procedure may hand us a string allocated after an |
98 | | * enclosing 'save' while the stream state itself predates that save; |
99 | | * if that save is restored, the string is freed but the stream survives, |
100 | | * so we must be able to detect this before using the string again. |
101 | | * The PLRM is explicit that PostScript program should not do this, and the |
102 | | * result is 'unpredictable'. |
103 | | */ |
104 | | static void |
105 | | s_proc_record_data(i_ctx_t *i_ctx_p, stream_proc_state *ss, const ref *pdata) |
106 | 1.95M | { |
107 | 1.95M | int space_index = r_space_index(pdata); |
108 | 1.95M | gs_ref_memory_t *mem; |
109 | | |
110 | 1.95M | ss->data_memory = NULL; |
111 | 1.95M | ss->data_save_id = 0; |
112 | 1.95M | if (space_index != i_vm_local) |
113 | 1.95M | return; /* global, foreign or system VM: not restorable */ |
114 | 0 | mem = idmemory->spaces_indexed[space_index]; |
115 | 0 | if (mem == NULL || mem->saved == NULL) |
116 | 0 | return; /* no save active: string cannot be restored away */ |
117 | 0 | ss->data_memory = mem; |
118 | | /* Save ids are unique and never reused; 0 means 'invisible' save. */ |
119 | 0 | ss->data_save_id = (mem->saved->id != 0 ? mem->saved->id : (ulong)-1); |
120 | 0 | } |
121 | | |
122 | | /* |
123 | | * Check that the string in ss->data has not been freed by a restore, |
124 | | * i.e. that the save that was innermost when it was stored is still |
125 | | * active. If it is not, the string may no longer exist: replace the |
126 | | * dangling reference with an empty string so it cannot be used. |
127 | | * Returns true if ss->data is safe to use. |
128 | | */ |
129 | | static bool |
130 | | s_proc_data_valid(stream_proc_state *ss) |
131 | 3.84M | { |
132 | 3.84M | const alloc_save_t *save; |
133 | | |
134 | 3.84M | if (ss->data_memory == NULL) |
135 | 3.84M | return true; |
136 | 0 | for (save = ss->data_memory->saved; save != NULL; |
137 | 0 | save = save->state.saved) |
138 | 0 | if (save->id == ss->data_save_id) |
139 | 0 | return true; |
140 | 0 | make_empty_string(&ss->data, a_all); |
141 | 0 | ss->index = 0; |
142 | 0 | ss->data_memory = NULL; |
143 | 0 | ss->data_save_id = 0; |
144 | 0 | return false; |
145 | 0 | } |
146 | | |
147 | | /* Handle an interrupt during a stream operation. */ |
148 | | /* This is logically unrelated to procedure streams, */ |
149 | | /* but it is also associated with the interpreter stream machinery. */ |
150 | | static int |
151 | | s_handle_intc(i_ctx_t *i_ctx_p, const ref *pstate, int nstate, |
152 | | op_proc_t cont) |
153 | 0 | { |
154 | 0 | int npush = nstate + 2; |
155 | |
|
156 | 0 | check_estack(npush); |
157 | 0 | if (nstate) |
158 | 0 | memcpy(esp + 2, pstate, nstate * sizeof(ref)); |
159 | | #if 0 /* **************** */ |
160 | | { |
161 | | int code = gs_interpret_error(gs_error_interrupt, (ref *) (esp + npush)); |
162 | | |
163 | | if (code < 0) |
164 | | return code; |
165 | | } |
166 | | #else /* **************** */ |
167 | 0 | npush--; |
168 | 0 | #endif /* **************** */ |
169 | 0 | make_op_estack(esp + 1, cont); |
170 | 0 | esp += npush; |
171 | 0 | return o_push_estack; |
172 | 0 | } |
173 | | |
174 | | /* Set default parameter values (actually, just clear pointers). */ |
175 | | static void |
176 | | s_proc_set_defaults(stream_state * st) |
177 | 0 | { |
178 | 0 | stream_proc_state *const ss = (stream_proc_state *) st; |
179 | |
|
180 | 0 | make_null(&ss->proc); |
181 | 0 | make_null(&ss->data); |
182 | 0 | } |
183 | | |
184 | | static int s_proc_copy_string(i_ctx_t * i_ctx_p, ref *dest, ref *src, uint mem) |
185 | 0 | { |
186 | 0 | int code = 0; |
187 | 0 | uint saved_space = avm_local; |
188 | |
|
189 | 0 | saved_space = imemory_space(iimemory); |
190 | |
|
191 | 0 | if (imemory_space(iimemory) != mem) |
192 | 0 | ialloc_set_space(idmemory, mem); |
193 | |
|
194 | 0 | code = gs_alloc_string_ref(iimemory, dest, 0, r_size(src), "copy_cspace_string"); |
195 | |
|
196 | 0 | if (imemory_space(iimemory) != saved_space) |
197 | 0 | ialloc_set_space(idmemory, saved_space); |
198 | |
|
199 | 0 | if (code < 0) |
200 | 0 | return code; |
201 | | |
202 | 0 | r_copy_attrs(dest, a_all, src); |
203 | |
|
204 | 0 | memcpy(dest->value.bytes, src->value.bytes, r_size(src)); |
205 | 0 | return 0; |
206 | 0 | } |
207 | | |
208 | | /* ---------------- Read streams ---------------- */ |
209 | | |
210 | | /* Forward references */ |
211 | | static stream_proc_process(s_proc_read_process); |
212 | | static int s_proc_read_continue(i_ctx_t *); |
213 | | |
214 | | /* Stream templates */ |
215 | | static const stream_template s_proc_read_template = { |
216 | | &st_sproc_state, NULL, s_proc_read_process, 1, 1, |
217 | | NULL, s_proc_set_defaults |
218 | | }; |
219 | | static const stream_procs s_proc_read_procs = { |
220 | | s_std_noavailable, s_std_noseek, s_std_read_reset, |
221 | | s_std_read_flush, s_std_null, NULL |
222 | | }; |
223 | | |
224 | | /* Allocate and open a procedure-based read stream. */ |
225 | | /* The caller must have checked that *sop is a procedure. */ |
226 | | int |
227 | | sread_proc(ref * sop, stream ** psstrm, gs_ref_memory_t *imem) |
228 | 960k | { |
229 | 960k | int code = |
230 | 960k | s_proc_init(sop, psstrm, s_mode_read, &s_proc_read_template, |
231 | 960k | &s_proc_read_procs, imem); |
232 | | |
233 | 960k | if (code < 0) |
234 | 0 | return code; |
235 | 960k | (*psstrm)->end_status = CALLC; |
236 | 960k | return code; |
237 | 960k | } |
238 | | |
239 | | /* Handle an input request. */ |
240 | | static int |
241 | | s_proc_read_process(stream_state * st, stream_cursor_read * ignore_pr, |
242 | | stream_cursor_write * pw, bool last) |
243 | 3.36M | { |
244 | | /* Move data from the string returned by the procedure */ |
245 | | /* into the stream buffer, or ask for a callback. */ |
246 | 3.36M | stream_proc_state *const ss = (stream_proc_state *) st; |
247 | 3.36M | uint count; |
248 | | |
249 | 3.36M | if (!s_proc_data_valid(ss)) |
250 | 0 | return_error(gs_error_ioerror); |
251 | 3.36M | count = r_size(&ss->data) - ss->index; |
252 | | |
253 | 3.36M | if (count > 0) { |
254 | 1.61M | uint wcount = pw->limit - pw->ptr; |
255 | | |
256 | 1.61M | if (wcount < count) |
257 | 649k | count = wcount; |
258 | 1.61M | memcpy(pw->ptr + 1, ss->data.value.bytes + ss->index, count); |
259 | 1.61M | pw->ptr += count; |
260 | 1.61M | ss->index += count; |
261 | 1.61M | return 1; |
262 | 1.61M | } |
263 | 1.75M | return (ss->eof ? EOFC : CALLC); |
264 | 3.36M | } |
265 | | |
266 | | /* Handle an exception (INTC or CALLC) from a read stream */ |
267 | | /* whose buffer is empty. */ |
268 | | int |
269 | | s_handle_read_exception(i_ctx_t *i_ctx_p, int status, const ref * fop, |
270 | | const ref * pstate, int nstate, op_proc_t cont) |
271 | 1.75M | { |
272 | 1.75M | int npush = nstate + 4; |
273 | 1.75M | stream *ps; |
274 | | |
275 | 1.75M | switch (status) { |
276 | 0 | case INTC: |
277 | 0 | return s_handle_intc(i_ctx_p, pstate, nstate, cont); |
278 | 1.75M | case CALLC: |
279 | 1.75M | break; |
280 | 0 | default: |
281 | 0 | return_error(gs_error_ioerror); |
282 | 1.75M | } |
283 | | /* Find the stream whose buffer needs refilling. */ |
284 | 3.51M | for (ps = fptr(fop); ps->strm != 0;) |
285 | 1.75M | ps = ps->strm; |
286 | 1.75M | check_estack(npush); |
287 | 1.75M | if (nstate) |
288 | 1.75M | memcpy(esp + 2, pstate, nstate * sizeof(ref)); |
289 | 1.75M | make_op_estack(esp + 1, cont); |
290 | 1.75M | esp += npush; |
291 | 1.75M | make_op_estack(esp - 2, s_proc_read_continue); |
292 | 1.75M | esp[-1] = *fop; |
293 | 1.75M | r_clear_attrs(esp - 1, a_executable); |
294 | 1.75M | *esp = ((stream_proc_state *) ps->state)->proc; |
295 | 1.75M | return o_push_estack; |
296 | 1.75M | } |
297 | | /* Continue a read operation after returning from a procedure callout. */ |
298 | | /* osp[0] contains the file (pushed on the e-stack by handle_read_status); */ |
299 | | /* osp[-1] contains the new data string (pushed by the procedure). */ |
300 | | /* The top of the e-stack contains the real continuation. */ |
301 | | static int |
302 | | s_proc_read_continue(i_ctx_t *i_ctx_p) |
303 | 1.75M | { |
304 | 1.75M | os_ptr op = osp; |
305 | 1.75M | os_ptr opbuf = op - 1; |
306 | 1.75M | stream *ps; |
307 | 1.75M | stream_proc_state *ss; |
308 | | |
309 | 1.75M | check_file(ps, op); |
310 | 1.75M | check_read_type(*opbuf, t_string); |
311 | 3.51M | while ((ps->end_status = 0, ps->strm) != 0) |
312 | 1.75M | ps = ps->strm; |
313 | 1.75M | if (!r_is_local(op) && r_is_local(opbuf)) { |
314 | 0 | ref copy; |
315 | 0 | int code = 0; |
316 | |
|
317 | 0 | code = s_proc_copy_string(i_ctx_p, ©, opbuf, r_space(op)); |
318 | 0 | if (code < 0) |
319 | 0 | return code; |
320 | 0 | *opbuf = copy; |
321 | 0 | } |
322 | 1.75M | ss = (stream_proc_state *) ps->state; |
323 | 1.75M | ss->data = *opbuf; |
324 | 1.75M | s_proc_record_data(i_ctx_p, ss, opbuf); |
325 | 1.75M | ss->index = 0; |
326 | 1.75M | if (r_size(opbuf) == 0) |
327 | 798k | ss->eof = true; |
328 | 1.75M | pop(2); |
329 | 1.75M | return 0; |
330 | 1.75M | } |
331 | | |
332 | | /* ---------------- Write streams ---------------- */ |
333 | | |
334 | | /* Forward references */ |
335 | | static stream_proc_flush(s_proc_write_flush); |
336 | | static stream_proc_process(s_proc_write_process); |
337 | | static int s_proc_write_continue(i_ctx_t *); |
338 | | |
339 | | /* Stream templates */ |
340 | | static const stream_template s_proc_write_template = { |
341 | | &st_sproc_state, NULL, s_proc_write_process, 1, 1, |
342 | | NULL, s_proc_set_defaults |
343 | | }; |
344 | | static const stream_procs s_proc_write_procs = { |
345 | | s_std_noavailable, s_std_noseek, s_std_write_reset, |
346 | | s_proc_write_flush, s_std_null, NULL |
347 | | }; |
348 | | |
349 | | /* Allocate and open a procedure-based write stream. */ |
350 | | /* The caller must have checked that *sop is a procedure. */ |
351 | | int |
352 | | swrite_proc(ref * sop, stream ** psstrm, gs_ref_memory_t *imem) |
353 | 74.5k | { |
354 | 74.5k | return s_proc_init(sop, psstrm, s_mode_write, &s_proc_write_template, |
355 | 74.5k | &s_proc_write_procs, imem); |
356 | 74.5k | } |
357 | | |
358 | | /* Handle an output request. */ |
359 | | static int |
360 | | s_proc_write_process(stream_state * st, stream_cursor_read * pr, |
361 | | stream_cursor_write * ignore_pw, bool last) |
362 | 272k | { |
363 | | /* Move data from the stream buffer to the string */ |
364 | | /* returned by the procedure, or ask for a callback. */ |
365 | 272k | stream_proc_state *const ss = (stream_proc_state *) st; |
366 | 272k | uint rcount = pr->limit - pr->ptr; |
367 | | |
368 | 272k | if (!s_proc_data_valid(ss)) |
369 | 0 | return_error(gs_error_ioerror); |
370 | | /* if 'last' return CALLC even when rcount == 0. ss->eof terminates */ |
371 | 272k | if (rcount > 0 || (last && !ss->eof)) { |
372 | 198k | uint wcount = r_size(&ss->data) - ss->index; |
373 | 198k | uint count = min(rcount, wcount); |
374 | | |
375 | 198k | memcpy(ss->data.value.bytes + ss->index, pr->ptr + 1, count); |
376 | 198k | pr->ptr += count; |
377 | 198k | ss->index += count; |
378 | 198k | if (rcount > wcount) |
379 | 123k | return CALLC; |
380 | 74.5k | else if (last) { |
381 | 74.5k | ss->eof = true; |
382 | 74.5k | return CALLC; |
383 | 74.5k | } else |
384 | 0 | return 0; |
385 | 198k | } |
386 | 74.5k | return ((ss->eof = last) ? EOFC : 0); |
387 | 272k | } |
388 | | |
389 | | /* Flush the output. This is non-standard because it must call the */ |
390 | | /* procedure. */ |
391 | | static int |
392 | | s_proc_write_flush(stream *s) |
393 | 0 | { |
394 | 0 | int result = s_process_write_buf(s, false); |
395 | 0 | stream_proc_state *const ss = (stream_proc_state *)s->state; |
396 | |
|
397 | 0 | return (result < 0 || ss->index == 0 ? result : CALLC); |
398 | 0 | } |
399 | | |
400 | | /* Handle an exception (INTC or CALLC) from a write stream */ |
401 | | /* whose buffer is full. */ |
402 | | int |
403 | | s_handle_write_exception(i_ctx_t *i_ctx_p, int status, const ref * fop, |
404 | | const ref * pstate, int nstate, op_proc_t cont) |
405 | 198k | { |
406 | 198k | stream *ps; |
407 | 198k | stream_proc_state *psst; |
408 | | |
409 | 198k | switch (status) { |
410 | 0 | case INTC: |
411 | 0 | return s_handle_intc(i_ctx_p, pstate, nstate, cont); |
412 | 198k | case CALLC: |
413 | 198k | break; |
414 | 0 | default: |
415 | 0 | return_error(gs_error_ioerror); |
416 | 198k | } |
417 | | /* Find the stream whose buffer needs emptying. */ |
418 | 396k | for (ps = fptr(fop); ps->strm != 0;) |
419 | 198k | ps = ps->strm; |
420 | 198k | psst = (stream_proc_state *) ps->state; |
421 | 198k | if (!s_proc_data_valid(psst)) |
422 | 0 | return_error(gs_error_ioerror); |
423 | 198k | { |
424 | 198k | int npush = nstate + 6; |
425 | | |
426 | 198k | check_estack(npush); |
427 | 198k | if (nstate) |
428 | 0 | memcpy(esp + 2, pstate, nstate * sizeof(ref)); |
429 | 198k | make_op_estack(esp + 1, cont); |
430 | 198k | esp += npush; |
431 | 198k | make_op_estack(esp - 4, s_proc_write_continue); |
432 | 198k | esp[-3] = *fop; |
433 | 198k | r_clear_attrs(esp - 3, a_executable); |
434 | 198k | make_bool(esp - 1, !psst->eof); |
435 | 198k | } |
436 | 198k | esp[-2] = psst->proc; |
437 | 198k | *esp = psst->data; |
438 | 198k | r_set_size(esp, psst->index); |
439 | 198k | return o_push_estack; |
440 | 198k | } |
441 | | /* Continue a write operation after returning from a procedure callout. */ |
442 | | /* osp[0] contains the file (pushed on the e-stack by handle_write_status); */ |
443 | | /* osp[-1] contains the new buffer string (pushed by the procedure). */ |
444 | | /* The top of the e-stack contains the real continuation. */ |
445 | | static int |
446 | | s_proc_write_continue(i_ctx_t *i_ctx_p) |
447 | 198k | { |
448 | 198k | os_ptr op = osp; |
449 | 198k | os_ptr opbuf = op - 1; |
450 | 198k | stream *ps; |
451 | 198k | stream_proc_state *ss; |
452 | | |
453 | 198k | check_file(ps, op); |
454 | 198k | check_write_type(*opbuf, t_string); |
455 | 396k | while (ps->strm != 0) { |
456 | 198k | if (ps->end_status == CALLC) |
457 | 0 | ps->end_status = 0; |
458 | 198k | ps = ps->strm; |
459 | 198k | } |
460 | 198k | ps->end_status = 0; |
461 | 198k | if (!r_is_local(op) && r_is_local(opbuf)) { |
462 | 0 | ref copy; |
463 | 0 | int code = 0; |
464 | |
|
465 | 0 | code = s_proc_copy_string(i_ctx_p, ©, opbuf, r_space(op)); |
466 | 0 | if (code < 0) |
467 | 0 | return code; |
468 | 0 | *opbuf = copy; |
469 | 0 | } |
470 | 198k | ss = (stream_proc_state *) ps->state; |
471 | 198k | ss->data = *opbuf; |
472 | 198k | s_proc_record_data(i_ctx_p, ss, opbuf); |
473 | 198k | ss->index = 0; |
474 | 198k | pop(2); |
475 | 198k | return 0; |
476 | 198k | } |
477 | | |
478 | | /* ------ More generic ------ */ |
479 | | |
480 | | /* Test whether a stream is procedure-based. */ |
481 | | bool |
482 | | s_is_proc(const stream *s) |
483 | 0 | { |
484 | 0 | return (s->procs.process == s_proc_read_process || |
485 | 0 | s->procs.process == s_proc_write_process); |
486 | 0 | } |
487 | | |
488 | | /* ------ Initialization procedure ------ */ |
489 | | |
490 | | const op_def zfproc_op_defs[] = |
491 | | { |
492 | | /* Internal operators */ |
493 | | {"2%s_proc_read_continue", s_proc_read_continue}, |
494 | | {"2%s_proc_write_continue", s_proc_write_continue}, |
495 | | op_def_end(0) |
496 | | }; |