/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 | 692k | { |
40 | 692k | stream_proc_state *const pptr = vptr; |
41 | | |
42 | 692k | r_clear_attrs(&pptr->proc, l_mark); |
43 | 692k | r_clear_attrs(&pptr->data, l_mark); |
44 | 692k | } |
45 | | static |
46 | 65.5k | ENUM_PTRS_WITH(sproc_enum_ptrs, stream_proc_state *pptr) return 0; |
47 | 21.8k | case 0: |
48 | 21.8k | ENUM_RETURN_REF(&pptr->proc); |
49 | 21.8k | case 1: |
50 | 21.8k | ENUM_RETURN_REF(&pptr->data); |
51 | 65.5k | ENUM_PTRS_END |
52 | 21.8k | static RELOC_PTRS_WITH(sproc_reloc_ptrs, stream_proc_state *pptr); |
53 | 21.8k | RELOC_REF_VAR(pptr->proc); |
54 | 21.8k | r_clear_attrs(&pptr->proc, l_mark); |
55 | 21.8k | RELOC_REF_VAR(pptr->data); |
56 | 21.8k | r_clear_attrs(&pptr->data, l_mark); |
57 | 21.8k | 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 | 942k | { |
69 | 942k | gs_memory_t *const mem = (gs_memory_t *)imem; |
70 | 942k | stream *sstrm = file_alloc_stream(mem, "s_proc_init(stream)"); |
71 | 942k | stream_proc_state *state = (stream_proc_state *) |
72 | 942k | s_alloc_state(mem, &st_sproc_state, "s_proc_init(state)"); |
73 | | |
74 | 942k | 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 | 942k | s_std_init(sstrm, NULL, 0, procs, mode); |
80 | 942k | sstrm->procs.process = temp->process; |
81 | 942k | state->templat = temp; |
82 | 942k | state->memory = mem; |
83 | 942k | state->eof = 0; |
84 | 942k | state->proc = *sop; |
85 | 942k | make_empty_string(&state->data, a_all); |
86 | 942k | state->data_memory = NULL; |
87 | 942k | state->data_save_id = 0; |
88 | 942k | state->index = 0; |
89 | 942k | sstrm->state = (stream_state *) state; |
90 | 942k | *psstrm = sstrm; |
91 | 942k | return 0; |
92 | 942k | } |
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.78M | { |
107 | 1.78M | int space_index = r_space_index(pdata); |
108 | 1.78M | gs_ref_memory_t *mem; |
109 | | |
110 | 1.78M | ss->data_memory = NULL; |
111 | 1.78M | ss->data_save_id = 0; |
112 | 1.78M | if (space_index != i_vm_local) |
113 | 1.78M | 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.49M | { |
132 | 3.49M | const alloc_save_t *save; |
133 | | |
134 | 3.49M | if (ss->data_memory == NULL) |
135 | 3.49M | 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 | 874k | { |
229 | 874k | int code = |
230 | 874k | s_proc_init(sop, psstrm, s_mode_read, &s_proc_read_template, |
231 | 874k | &s_proc_read_procs, imem); |
232 | | |
233 | 874k | if (code < 0) |
234 | 0 | return code; |
235 | 874k | (*psstrm)->end_status = CALLC; |
236 | 874k | return code; |
237 | 874k | } |
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.06M | { |
244 | | /* Move data from the string returned by the procedure */ |
245 | | /* into the stream buffer, or ask for a callback. */ |
246 | 3.06M | stream_proc_state *const ss = (stream_proc_state *) st; |
247 | 3.06M | uint count; |
248 | | |
249 | 3.06M | if (!s_proc_data_valid(ss)) |
250 | 0 | return_error(gs_error_ioerror); |
251 | 3.06M | count = r_size(&ss->data) - ss->index; |
252 | | |
253 | 3.06M | if (count > 0) { |
254 | 1.46M | uint wcount = pw->limit - pw->ptr; |
255 | | |
256 | 1.46M | if (wcount < count) |
257 | 588k | count = wcount; |
258 | 1.46M | memcpy(pw->ptr + 1, ss->data.value.bytes + ss->index, count); |
259 | 1.46M | pw->ptr += count; |
260 | 1.46M | ss->index += count; |
261 | 1.46M | return 1; |
262 | 1.46M | } |
263 | 1.60M | return (ss->eof ? EOFC : CALLC); |
264 | 3.06M | } |
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.60M | { |
272 | 1.60M | int npush = nstate + 4; |
273 | 1.60M | stream *ps; |
274 | | |
275 | 1.60M | switch (status) { |
276 | 0 | case INTC: |
277 | 0 | return s_handle_intc(i_ctx_p, pstate, nstate, cont); |
278 | 1.60M | case CALLC: |
279 | 1.60M | break; |
280 | 0 | default: |
281 | 0 | return_error(gs_error_ioerror); |
282 | 1.60M | } |
283 | | /* Find the stream whose buffer needs refilling. */ |
284 | 3.20M | for (ps = fptr(fop); ps->strm != 0;) |
285 | 1.60M | ps = ps->strm; |
286 | 1.60M | check_estack(npush); |
287 | 1.60M | if (nstate) |
288 | 1.60M | memcpy(esp + 2, pstate, nstate * sizeof(ref)); |
289 | 1.60M | make_op_estack(esp + 1, cont); |
290 | 1.60M | esp += npush; |
291 | 1.60M | make_op_estack(esp - 2, s_proc_read_continue); |
292 | 1.60M | esp[-1] = *fop; |
293 | 1.60M | r_clear_attrs(esp - 1, a_executable); |
294 | 1.60M | *esp = ((stream_proc_state *) ps->state)->proc; |
295 | 1.60M | return o_push_estack; |
296 | 1.60M | } |
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.60M | { |
304 | 1.60M | os_ptr op = osp; |
305 | 1.60M | os_ptr opbuf = op - 1; |
306 | 1.60M | stream *ps; |
307 | 1.60M | stream_proc_state *ss; |
308 | 1.60M | uint s1, s2; |
309 | | |
310 | 1.60M | check_file(ps, op); |
311 | 1.60M | check_read_type(*opbuf, t_string); |
312 | 3.20M | while ((ps->end_status = 0, ps->strm) != 0) |
313 | 1.60M | ps = ps->strm; |
314 | 1.60M | s1 = r_space(op); |
315 | 1.60M | s2 = r_space(opbuf); |
316 | 1.60M | if (!r_is_local(op) && r_is_local(opbuf)) { |
317 | 0 | ref copy; |
318 | 0 | int code = 0; |
319 | |
|
320 | 0 | code = s_proc_copy_string(i_ctx_p, ©, opbuf, r_space(op)); |
321 | 0 | if (code < 0) |
322 | 0 | return code; |
323 | 0 | *opbuf = copy; |
324 | 0 | } |
325 | 1.60M | ss = (stream_proc_state *) ps->state; |
326 | 1.60M | ss->data = *opbuf; |
327 | 1.60M | s_proc_record_data(i_ctx_p, ss, opbuf); |
328 | 1.60M | ss->index = 0; |
329 | 1.60M | if (r_size(opbuf) == 0) |
330 | 727k | ss->eof = true; |
331 | 1.60M | pop(2); |
332 | 1.60M | return 0; |
333 | 1.60M | } |
334 | | |
335 | | /* ---------------- Write streams ---------------- */ |
336 | | |
337 | | /* Forward references */ |
338 | | static stream_proc_flush(s_proc_write_flush); |
339 | | static stream_proc_process(s_proc_write_process); |
340 | | static int s_proc_write_continue(i_ctx_t *); |
341 | | |
342 | | /* Stream templates */ |
343 | | static const stream_template s_proc_write_template = { |
344 | | &st_sproc_state, NULL, s_proc_write_process, 1, 1, |
345 | | NULL, s_proc_set_defaults |
346 | | }; |
347 | | static const stream_procs s_proc_write_procs = { |
348 | | s_std_noavailable, s_std_noseek, s_std_write_reset, |
349 | | s_proc_write_flush, s_std_null, NULL |
350 | | }; |
351 | | |
352 | | /* Allocate and open a procedure-based write stream. */ |
353 | | /* The caller must have checked that *sop is a procedure. */ |
354 | | int |
355 | | swrite_proc(ref * sop, stream ** psstrm, gs_ref_memory_t *imem) |
356 | 67.5k | { |
357 | 67.5k | return s_proc_init(sop, psstrm, s_mode_write, &s_proc_write_template, |
358 | 67.5k | &s_proc_write_procs, imem); |
359 | 67.5k | } |
360 | | |
361 | | /* Handle an output request. */ |
362 | | static int |
363 | | s_proc_write_process(stream_state * st, stream_cursor_read * pr, |
364 | | stream_cursor_write * ignore_pw, bool last) |
365 | 250k | { |
366 | | /* Move data from the stream buffer to the string */ |
367 | | /* returned by the procedure, or ask for a callback. */ |
368 | 250k | stream_proc_state *const ss = (stream_proc_state *) st; |
369 | 250k | uint rcount = pr->limit - pr->ptr; |
370 | | |
371 | 250k | if (!s_proc_data_valid(ss)) |
372 | 0 | return_error(gs_error_ioerror); |
373 | | /* if 'last' return CALLC even when rcount == 0. ss->eof terminates */ |
374 | 250k | if (rcount > 0 || (last && !ss->eof)) { |
375 | 183k | uint wcount = r_size(&ss->data) - ss->index; |
376 | 183k | uint count = min(rcount, wcount); |
377 | | |
378 | 183k | memcpy(ss->data.value.bytes + ss->index, pr->ptr + 1, count); |
379 | 183k | pr->ptr += count; |
380 | 183k | ss->index += count; |
381 | 183k | if (rcount > wcount) |
382 | 115k | return CALLC; |
383 | 67.5k | else if (last) { |
384 | 67.5k | ss->eof = true; |
385 | 67.5k | return CALLC; |
386 | 67.5k | } else |
387 | 0 | return 0; |
388 | 183k | } |
389 | 67.5k | return ((ss->eof = last) ? EOFC : 0); |
390 | 250k | } |
391 | | |
392 | | /* Flush the output. This is non-standard because it must call the */ |
393 | | /* procedure. */ |
394 | | static int |
395 | | s_proc_write_flush(stream *s) |
396 | 0 | { |
397 | 0 | int result = s_process_write_buf(s, false); |
398 | 0 | stream_proc_state *const ss = (stream_proc_state *)s->state; |
399 | |
|
400 | 0 | return (result < 0 || ss->index == 0 ? result : CALLC); |
401 | 0 | } |
402 | | |
403 | | /* Handle an exception (INTC or CALLC) from a write stream */ |
404 | | /* whose buffer is full. */ |
405 | | int |
406 | | s_handle_write_exception(i_ctx_t *i_ctx_p, int status, const ref * fop, |
407 | | const ref * pstate, int nstate, op_proc_t cont) |
408 | 183k | { |
409 | 183k | stream *ps; |
410 | 183k | stream_proc_state *psst; |
411 | | |
412 | 183k | switch (status) { |
413 | 0 | case INTC: |
414 | 0 | return s_handle_intc(i_ctx_p, pstate, nstate, cont); |
415 | 183k | case CALLC: |
416 | 183k | break; |
417 | 0 | default: |
418 | 0 | return_error(gs_error_ioerror); |
419 | 183k | } |
420 | | /* Find the stream whose buffer needs emptying. */ |
421 | 366k | for (ps = fptr(fop); ps->strm != 0;) |
422 | 183k | ps = ps->strm; |
423 | 183k | psst = (stream_proc_state *) ps->state; |
424 | 183k | if (!s_proc_data_valid(psst)) |
425 | 0 | return_error(gs_error_ioerror); |
426 | 183k | { |
427 | 183k | int npush = nstate + 6; |
428 | | |
429 | 183k | check_estack(npush); |
430 | 183k | if (nstate) |
431 | 0 | memcpy(esp + 2, pstate, nstate * sizeof(ref)); |
432 | 183k | make_op_estack(esp + 1, cont); |
433 | 183k | esp += npush; |
434 | 183k | make_op_estack(esp - 4, s_proc_write_continue); |
435 | 183k | esp[-3] = *fop; |
436 | 183k | r_clear_attrs(esp - 3, a_executable); |
437 | 183k | make_bool(esp - 1, !psst->eof); |
438 | 183k | } |
439 | 183k | esp[-2] = psst->proc; |
440 | 183k | *esp = psst->data; |
441 | 183k | r_set_size(esp, psst->index); |
442 | 183k | return o_push_estack; |
443 | 183k | } |
444 | | /* Continue a write operation after returning from a procedure callout. */ |
445 | | /* osp[0] contains the file (pushed on the e-stack by handle_write_status); */ |
446 | | /* osp[-1] contains the new buffer string (pushed by the procedure). */ |
447 | | /* The top of the e-stack contains the real continuation. */ |
448 | | static int |
449 | | s_proc_write_continue(i_ctx_t *i_ctx_p) |
450 | 183k | { |
451 | 183k | os_ptr op = osp; |
452 | 183k | os_ptr opbuf = op - 1; |
453 | 183k | stream *ps; |
454 | 183k | stream_proc_state *ss; |
455 | | |
456 | 183k | check_file(ps, op); |
457 | 183k | check_write_type(*opbuf, t_string); |
458 | 366k | while (ps->strm != 0) { |
459 | 183k | if (ps->end_status == CALLC) |
460 | 0 | ps->end_status = 0; |
461 | 183k | ps = ps->strm; |
462 | 183k | } |
463 | 183k | ps->end_status = 0; |
464 | 183k | if (!r_is_local(op) && r_is_local(opbuf)) { |
465 | 0 | ref copy; |
466 | 0 | int code = 0; |
467 | |
|
468 | 0 | code = s_proc_copy_string(i_ctx_p, ©, opbuf, r_space(op)); |
469 | 0 | if (code < 0) |
470 | 0 | return code; |
471 | 0 | *opbuf = copy; |
472 | 0 | } |
473 | 183k | ss = (stream_proc_state *) ps->state; |
474 | 183k | ss->data = *opbuf; |
475 | 183k | s_proc_record_data(i_ctx_p, ss, opbuf); |
476 | 183k | ss->index = 0; |
477 | 183k | pop(2); |
478 | 183k | return 0; |
479 | 183k | } |
480 | | |
481 | | /* ------ More generic ------ */ |
482 | | |
483 | | /* Test whether a stream is procedure-based. */ |
484 | | bool |
485 | | s_is_proc(const stream *s) |
486 | 0 | { |
487 | 0 | return (s->procs.process == s_proc_read_process || |
488 | 0 | s->procs.process == s_proc_write_process); |
489 | 0 | } |
490 | | |
491 | | /* ------ Initialization procedure ------ */ |
492 | | |
493 | | const op_def zfproc_op_defs[] = |
494 | | { |
495 | | /* Internal operators */ |
496 | | {"2%s_proc_read_continue", s_proc_read_continue}, |
497 | | {"2%s_proc_write_continue", s_proc_write_continue}, |
498 | | op_def_end(0) |
499 | | }; |