xref: /plan9/sys/src/cmd/gs/src/zvmem.c (revision 593dc095aefb2a85c828727bbfa9da139a49bdf4)
1 /* Copyright (C) 1989, 1995, 1997, 1998, 1999 Aladdin Enterprises.  All rights reserved.
2 
3   This software is provided AS-IS with no warranty, either express or
4   implied.
5 
6   This software is distributed under license and may not be copied,
7   modified or distributed except as expressly authorized under the terms
8   of the license contained in the file LICENSE in this distribution.
9 
10   For more information about licensing, please refer to
11   http://www.ghostscript.com/licensing/. For information on
12   commercial licensing, go to http://www.artifex.com/licensing/ or
13   contact Artifex Software, Inc., 101 Lucas Valley Road #110,
14   San Rafael, CA  94903, U.S.A., +1(415)492-9861.
15 */
16 
17 /* $Id: zvmem.c,v 1.8 2004/08/04 19:36:13 stefan Exp $ */
18 /* "Virtual memory" operators */
19 #include "ghost.h"
20 #include "gsstruct.h"
21 #include "oper.h"
22 #include "estack.h"		/* for checking in restore */
23 #include "ialloc.h"
24 #include "idict.h"		/* ditto */
25 #include "igstate.h"
26 #include "isave.h"
27 #include "dstack.h"
28 #include "stream.h"		/* for files.h */
29 #include "files.h"		/* for e-stack processing */
30 #include "store.h"
31 #include "gsmatrix.h"		/* for gsstate.h */
32 #include "gsstate.h"
33 
34 /* Define whether we validate memory before/after save/restore. */
35 /* Note that we only actually do this if DEBUG is set and -Z? is selected. */
36 private const bool I_VALIDATE_BEFORE_SAVE = true;
37 private const bool I_VALIDATE_AFTER_SAVE = true;
38 private const bool I_VALIDATE_BEFORE_RESTORE = true;
39 private const bool I_VALIDATE_AFTER_RESTORE = true;
40 
41 /* 'Save' structure */
42 typedef struct vm_save_s vm_save_t;
43 struct vm_save_s {
44     gs_state *gsave;		/* old graphics state */
45 };
46 
47 gs_private_st_ptrs1(st_vm_save, vm_save_t, "savetype",
48 		    vm_save_enum_ptrs, vm_save_reloc_ptrs, gsave);
49 
50 /* Clean up the stacks and validate storage. */
51 private void
ivalidate_clean_spaces(i_ctx_t * i_ctx_p)52 ivalidate_clean_spaces(i_ctx_t *i_ctx_p)
53 {
54     if (gs_debug_c('?')) {
55 	ref_stack_cleanup(&d_stack);
56 	ref_stack_cleanup(&e_stack);
57 	ref_stack_cleanup(&o_stack);
58 	ivalidate_spaces();
59     }
60 }
61 
62 /* - save <save> */
63 int
zsave(i_ctx_t * i_ctx_p)64 zsave(i_ctx_t *i_ctx_p)
65 {
66     os_ptr op = osp;
67     uint space = icurrent_space;
68     vm_save_t *vmsave;
69     ulong sid;
70     int code;
71     gs_state *prev;
72 
73     if (I_VALIDATE_BEFORE_SAVE)
74 	ivalidate_clean_spaces(i_ctx_p);
75     ialloc_set_space(idmemory, avm_local);
76     vmsave = ialloc_struct(vm_save_t, &st_vm_save, "zsave");
77     ialloc_set_space(idmemory, space);
78     if (vmsave == 0)
79 	return_error(e_VMerror);
80     sid = alloc_save_state(idmemory, vmsave);
81     if (sid == 0) {
82 	ifree_object(vmsave, "zsave");
83 	return_error(e_VMerror);
84     }
85     if_debug2('u', "[u]vmsave 0x%lx, id = %lu\n",
86 	      (ulong) vmsave, (ulong) sid);
87     code = gs_gsave_for_save(igs, &prev);
88     if (code < 0)
89 	return code;
90     code = gs_gsave(igs);
91     if (code < 0)
92 	return code;
93     vmsave->gsave = prev;
94     push(1);
95     make_tav(op, t_save, 0, saveid, sid);
96     if (I_VALIDATE_AFTER_SAVE)
97 	ivalidate_clean_spaces(i_ctx_p);
98     return 0;
99 }
100 
101 /* <save> restore - */
102 private int restore_check_operand(os_ptr, alloc_save_t **, gs_dual_memory_t *);
103 private int restore_check_stack(const ref_stack_t *, const alloc_save_t *, bool);
104 private void restore_fix_stack(ref_stack_t *, const alloc_save_t *, bool);
105 int
zrestore(i_ctx_t * i_ctx_p)106 zrestore(i_ctx_t *i_ctx_p)
107 {
108     os_ptr op = osp;
109     alloc_save_t *asave;
110     bool last;
111     vm_save_t *vmsave;
112     int code = restore_check_operand(op, &asave, idmemory);
113 
114     if (code < 0)
115 	return code;
116     if_debug2('u', "[u]vmrestore 0x%lx, id = %lu\n",
117 	      (ulong) alloc_save_client_data(asave),
118 	      (ulong) op->value.saveid);
119     if (I_VALIDATE_BEFORE_RESTORE)
120 	ivalidate_clean_spaces(i_ctx_p);
121     /* Check the contents of the stacks. */
122     osp--;
123     {
124 	int code;
125 
126 	if ((code = restore_check_stack(&o_stack, asave, false)) < 0 ||
127 	    (code = restore_check_stack(&e_stack, asave, true)) < 0 ||
128 	    (code = restore_check_stack(&d_stack, asave, false)) < 0
129 	    ) {
130 	    osp++;
131 	    return code;
132 	}
133     }
134     /* Reset l_new in all stack entries if the new save level is zero. */
135     /* Also do some special fixing on the e-stack. */
136     restore_fix_stack(&o_stack, asave, false);
137     restore_fix_stack(&e_stack, asave, true);
138     restore_fix_stack(&d_stack, asave, false);
139     /* Iteratively restore the state of memory, */
140     /* also doing a grestoreall at each step. */
141     do {
142 	vmsave = alloc_save_client_data(alloc_save_current(idmemory));
143 	/* Restore the graphics state. */
144 	gs_grestoreall_for_restore(igs, vmsave->gsave);
145 	/*
146 	 * If alloc_save_space decided to do a second save, the vmsave
147 	 * object was allocated one save level less deep than the
148 	 * current level, so ifree_object won't actually free it;
149 	 * however, it points to a gsave object that definitely
150 	 * *has* been freed.  In order not to trip up the garbage
151 	 * collector, we clear the gsave pointer now.
152 	 */
153 	vmsave->gsave = 0;
154 	/* Now it's safe to restore the state of memory. */
155 	last = alloc_restore_state_step(asave);
156     }
157     while (!last);
158     {
159 	uint space = icurrent_space;
160 
161 	ialloc_set_space(idmemory, avm_local);
162 	ifree_object(vmsave, "zrestore");
163 	ialloc_set_space(idmemory, space);
164     }
165     dict_set_top();		/* reload dict stack cache */
166     if (I_VALIDATE_AFTER_RESTORE)
167 	ivalidate_clean_spaces(i_ctx_p);
168     /* If the i_ctx_p LockFilePermissions is true, but the userparams */
169     /* we just restored is false, we need to make sure that we do not */
170     /* cause an 'invalidaccess' in setuserparams. Temporarily set     */
171     /* LockFilePermissions false until the gs_lev2.ps can do a        */
172     /* setuserparams from the restored userparam dictionary.          */
173     i_ctx_p->LockFilePermissions = false;
174     return 0;
175 }
176 /* Check the operand of a restore. */
177 private int
restore_check_operand(os_ptr op,alloc_save_t ** pasave,gs_dual_memory_t * idmem)178 restore_check_operand(os_ptr op, alloc_save_t ** pasave,
179 		      gs_dual_memory_t *idmem)
180 {
181     vm_save_t *vmsave;
182     ulong sid;
183     alloc_save_t *asave;
184 
185     check_type(*op, t_save);
186     vmsave = r_ptr(op, vm_save_t);
187     if (vmsave == 0)		/* invalidated save */
188 	return_error(e_invalidrestore);
189     sid = op->value.saveid;
190     asave = alloc_find_save(idmem, sid);
191     if (asave == 0)
192 	return_error(e_invalidrestore);
193     *pasave = asave;
194     return 0;
195 }
196 /* Check a stack to make sure all its elements are older than a save. */
197 private int
restore_check_stack(const ref_stack_t * pstack,const alloc_save_t * asave,bool is_estack)198 restore_check_stack(const ref_stack_t * pstack, const alloc_save_t * asave,
199 		    bool is_estack)
200 {
201     ref_stack_enum_t rsenum;
202 
203     ref_stack_enum_begin(&rsenum, pstack);
204     do {
205 	const ref *stkp = rsenum.ptr;
206 	uint size = rsenum.size;
207 
208 	for (; size; stkp++, size--) {
209 	    const void *ptr;
210 
211 	    switch (r_type(stkp)) {
212 		case t_array:
213 		    ptr = stkp->value.refs;
214 		    break;
215 		case t_dictionary:
216 		    ptr = stkp->value.pdict;
217 		    break;
218 		case t_file:
219 		    /* Don't check executable or closed literal */
220 		    /* files on the e-stack. */
221 		    {
222 			stream *s;
223 
224 			if (is_estack &&
225 			    (r_has_attr(stkp, a_executable) ||
226 			     file_is_invalid(s, stkp))
227 			    )
228 			    continue;
229 		    }
230 		    ptr = stkp->value.pfile;
231 		    break;
232 		case t_name:
233 		    /* Names are special because of how they are allocated. */
234 		    if (alloc_name_is_since_save((const gs_memory_t *)pstack->memory,
235 						 stkp, asave))
236 			return_error(e_invalidrestore);
237 		    continue;
238 		case t_string:
239 		    /* Don't check empty executable strings */
240 		    /* on the e-stack. */
241 		    if (r_size(stkp) == 0 &&
242 			r_has_attr(stkp, a_executable) && is_estack
243 			)
244 			continue;
245 		    ptr = stkp->value.bytes;
246 		    break;
247 		case t_mixedarray:
248 		case t_shortarray:
249 		    ptr = stkp->value.packed;
250 		    break;
251 		case t_device:
252 		    ptr = stkp->value.pdevice;
253 		    break;
254 		case t_fontID:
255 		case t_struct:
256 		case t_astruct:
257 		    ptr = stkp->value.pstruct;
258 		    break;
259 		default:
260 		    continue;
261 	    }
262 	    if (alloc_is_since_save(ptr, asave))
263 		return_error(e_invalidrestore);
264 	}
265     } while (ref_stack_enum_next(&rsenum));
266     return 0;		/* OK */
267 }
268 /*
269  * If the new save level is zero, fix up the contents of a stack
270  * by clearing the l_new bit in all the entries (since we can't tolerate
271  * values with l_new set if the save level is zero).
272  * Also, in any case, fix up the e-stack by replacing empty executable
273  * strings and closed executable files that are newer than the save
274  * with canonical ones that aren't.
275  *
276  * Note that this procedure is only called if restore_check_stack succeeded.
277  */
278 private void
restore_fix_stack(ref_stack_t * pstack,const alloc_save_t * asave,bool is_estack)279 restore_fix_stack(ref_stack_t * pstack, const alloc_save_t * asave,
280 		  bool is_estack)
281 {
282     ref_stack_enum_t rsenum;
283 
284     ref_stack_enum_begin(&rsenum, pstack);
285     do {
286 	ref *stkp = rsenum.ptr;
287 	uint size = rsenum.size;
288 
289 	for (; size; stkp++, size--) {
290 	    r_clear_attrs(stkp, l_new);		/* always do it, no harm */
291 	    if (is_estack) {
292 		ref ofile;
293 
294 		ref_assign(&ofile, stkp);
295 		switch (r_type(stkp)) {
296 		    case t_string:
297 			if (r_size(stkp) == 0 &&
298 			    alloc_is_since_save(stkp->value.bytes,
299 						asave)
300 			    ) {
301 			    make_empty_const_string(stkp,
302 						    avm_foreign);
303 			    break;
304 			}
305 			continue;
306 		    case t_file:
307 			if (alloc_is_since_save(stkp->value.pfile,
308 						asave)
309 			    ) {
310 			    make_invalid_file(stkp);
311 			    break;
312 			}
313 			continue;
314 		    default:
315 			continue;
316 		}
317 		r_copy_attrs(stkp, a_all | a_executable,
318 			     &ofile);
319 	    }
320 	}
321     } while (ref_stack_enum_next(&rsenum));
322 }
323 
324 /* - vmstatus <save_level> <vm_used> <vm_maximum> */
325 private int
zvmstatus(i_ctx_t * i_ctx_p)326 zvmstatus(i_ctx_t *i_ctx_p)
327 {
328     os_ptr op = osp;
329     gs_memory_status_t mstat, dstat;
330 
331     gs_memory_status(imemory, &mstat);
332     if (imemory == imemory_global) {
333 	gs_memory_status_t sstat;
334 
335 	gs_memory_status(imemory_system, &sstat);
336 	mstat.allocated += sstat.allocated;
337 	mstat.used += sstat.used;
338     }
339     gs_memory_status(imemory->non_gc_memory, &dstat);
340     push(3);
341     make_int(op - 2, imemory_save_level(iimemory_local));
342     make_int(op - 1, mstat.used);
343     make_int(op, mstat.allocated + dstat.allocated - dstat.used);
344     return 0;
345 }
346 
347 /* ------ Non-standard extensions ------ */
348 
349 /* <save> .forgetsave - */
350 private int
zforgetsave(i_ctx_t * i_ctx_p)351 zforgetsave(i_ctx_t *i_ctx_p)
352 {
353     os_ptr op = osp;
354     alloc_save_t *asave;
355     vm_save_t *vmsave;
356     int code = restore_check_operand(op, &asave, idmemory);
357 
358     if (code < 0)
359 	return 0;
360     vmsave = alloc_save_client_data(asave);
361     /* Reset l_new in all stack entries if the new save level is zero. */
362     restore_fix_stack(&o_stack, asave, false);
363     restore_fix_stack(&e_stack, asave, false);
364     restore_fix_stack(&d_stack, asave, false);
365     /*
366      * Forget the gsaves, by deleting the bottom gstate on
367      * the current stack and the top one on the saved stack and then
368      * concatenating the stacks together.
369      */
370     {
371 	gs_state *pgs = igs;
372 	gs_state *last;
373 
374 	while (gs_state_saved(last = gs_state_saved(pgs)) != 0)
375 	    pgs = last;
376 	gs_state_swap_saved(last, vmsave->gsave);
377 	gs_grestore(last);
378 	gs_grestore(last);
379     }
380     /* Forget the save in the memory manager. */
381     alloc_forget_save(asave);
382     {
383 	uint space = icurrent_space;
384 
385 	ialloc_set_space(idmemory, avm_local);
386 	/* See above for why we clear the gsave pointer here. */
387 	vmsave->gsave = 0;
388 	ifree_object(vmsave, "zrestore");
389 	ialloc_set_space(idmemory, space);
390     }
391     pop(1);
392     return 0;
393 }
394 
395 /* ------ Initialization procedure ------ */
396 
397 const op_def zvmem_op_defs[] =
398 {
399     {"1.forgetsave", zforgetsave},
400     {"1restore", zrestore},
401     {"0save", zsave},
402     {"0vmstatus", zvmstatus},
403     op_def_end(0)
404 };
405