xref: /plan9/sys/src/cmd/gs/src/gxclmem.c (revision 593dc095aefb2a85c828727bbfa9da139a49bdf4)
1 /* Copyright (C) 1995, 1996, 1997, 1998 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: gxclmem.c,v 1.5 2002/06/16 05:48:55 lpd Exp $ */
18 /* RAM-based command list implementation */
19 #include "memory_.h"
20 #include "gx.h"
21 #include "gserrors.h"
22 #include "gxclmem.h"
23 
24 /*
25  * Based on: memfile.c        Version: 1.4 3/21/95 14:59:33 by Ray Johnston.
26  * Copyright assigned to Aladdin Enterprises.
27  */
28 
29 /*****************************************************************************
30 
31    This package is more or less optimal for use by the clist routines, with
32    a couple of the more likely to change "tuning" parameters given in the
33    two macros below -- NEED_TO_COMPRESS and GET_NUM_RAW_BUFFERS. Usually
34    the NEED_TO_COMPRESS decision will be deferred as long as possible based
35    on some total system free RAM space remaining.
36 
37    The data structures are in "memfile.h", and the primary 'tuning' parameter
38    is MEMFILE_DATA_SIZE. This should not be too small to keep the overhead
39    ratio of the block structures to the clist data small. A value of 16384
40    is probably in the ballpark.
41 
42    The concept is that a memory based "file" is created initially without
43    compression, with index blocks every MEMFILE_DATA_SIZE of the file. The
44    primary blocks (used by the memfile_fseek logic) for indexing into the
45    file are called 'logical' (LOG_MEMFILE_BLK) and the data in stored in a
46    different block called a 'physical' block (PHYS_MEMFILE_BLK). When the
47    file is not yet compressed, indicated by (f->phys_curr==NULL), then there
48    is one physical block for each logical block. The physical block also has
49    the 'data_limit' set to NULL if the data is not compressed. Thus when a
50    file is not compressed there is one physical block for each logical block.
51 
52 COMPRESSION.
53 
54    When compression is triggered for a file then all of the blocks except
55    the last are compressed.  Compression will result in a physical block
56    that holds data for more than one logical block. Each logical block now
57    points to the start of compressed data in a physical block with the
58    'phys_pdata' pointer. The 'data_limit' pointer in the physical block is
59    where the compression logic stopped storing data (as stream data
60    compressors are allowed to do). The data for the logical block may span
61    to the next physical block. Once physical blocks are compressed, they are
62    chained together using the 'link' field.
63 
64    The 'f->phys_curr' points to the block being filled by compression, with
65    the 'f->wt.ptr' pointing to the last byte filled in the block. These are
66    used during subsequent compression when the last logical block of the
67    file fills the physical block.
68 
69 DECOMPRESSION.
70 
71    During reading the clist, if the logical block points to an uncompressed
72    physical block, then 'memfile_get_pdata' simply sets the 'pdata' and the
73    'pdata_end' pointers. If the logical block was compressed, then it may
74    still be resident in a cache of decompression buffers. The number of these
75    decompression buffers is not critical -- even one is enough, but having
76    more may prevent decompressing blocks more than once (a cache_miss). The
77    number of decompression buffers, called "raw" buffers, that are attempted
78    to allocate can be changed with the GET_NUM_RAW_BUFFERS macro, but no
79    error occurs if less than that number can be allocated.
80 
81    If the logical block still resides in a decompression cache buffer, then
82    the 'raw_block' will identify the block. If the data for a logical block
83    only exists in compressed form, then the "tail" of the list of decompression
84    buffers is re-used, marking the 'raw_block' of the logical block that was
85    previously associated with this data to NULL.
86 
87    Whichever raw decompression buffer is accessed is moved to the head of the
88    decompression buffer list in order to keep the tail of the list as the
89    "least recently used".
90 
91    There are some DEBUG global static variables used to count the number of
92    cache hits "tot_cache_hits" and the number of times a logical block is
93    decompressed "tot_cache_miss". Note that the actual number of cache miss
94    events is 'f->log_length/MEMFILE_DATA_SIZE - tot_cache_miss' since we
95    assume that every logical block must be decmpressed at least once.
96 
97    Empirical results so far indicate that if one cache raw buffer for every
98    32 logical blocks, then the hit/miss ratio exceeds 99%. Of course, the
99    number of raw buffers should be more than 1 if possible, and in many
100    implementations (single threaded), the memory usage does not increase
101    during the page output step so almost all of memory can be used for
102    these raw buffers to prevent the likelihood of a cache miss.
103 
104    Of course, this is dependent on reasonably efficient clist blocking
105    during writing which is dependent on the data and on the BufferSpace
106    value which determines the number of clist band data buffers available.
107    Empirical testing shows that the overall efficiency is best if the
108    BufferSpace value is 1,000,000 (as in the original Ghostscript source).
109    [Note: I expected to be able to use smaller buffer sizes for some cases,
110     but this resulted in a high level of thrashing...RJJ]
111 
112 LIMITATIONS.
113 
114    The most serious limitation is caused by the way 'memfile_fwrite' decides
115    to free up and re-initialize a file. If memfile_fwrite is called after
116    a seek to any location except the start of the file, then an error is
117    issued since logic is not present to properly free up on a partial file.
118    This is not a problem as used by the 'clist' logic since rewind is used
119    to position to the start of a file when re-using it after an 'erasepage'.
120 
121    Since the 'clist' logic always traverses the clist using fseek's to ever
122    increasing locations, no optimizations of backward seeks was implemented.
123    This would be relatively easy with back chain links or bi-directional
124    "X-OR" pointer information to link the logical block chain. The rewind
125    function is optimal and moves directly to the start of the file.
126 
127 ********************************************************************************/
128 
129 /*
130    The need to compress should be conditional on the amount of available
131    memory, but we don't have a way to communicate this to these routines.
132    Instead, we simply start compressing when we've allocated more than
133    COMPRESSION_THRESHOLD amount of data.  The threshold should be at
134    least as large as the fixed overhead of the compressor plus the
135    decompressor, plus the expected compressed size of a block that size.
136  */
137 private const long COMPRESSION_THRESHOLD = 300000;
138 
139 #define NEED_TO_COMPRESS(f)\
140   ((f)->ok_to_compress && (f)->total_space > COMPRESSION_THRESHOLD)
141 
142    /* FOR NOW ALLOCATE 1 raw buffer for every 32 blocks (at least 8)    */
143 #define GET_NUM_RAW_BUFFERS( f ) 					\
144 	 max(f->log_length/MEMFILE_DATA_SIZE/32, 8)
145 
146 #define MALLOC(f, siz, cname)\
147   (void *)gs_alloc_bytes((f)->data_memory, siz, cname)
148 #define FREE(f, obj, cname)\
149   (gs_free_object((f)->data_memory, obj, cname),\
150    (f)->total_space -= sizeof(*(obj)))
151 
152 /* Structure descriptor for GC */
153 private_st_MEMFILE();
154 
155 	/* forward references */
156 private void memfile_free_mem(MEMFILE * f);
157 private int memfile_init_empty(MEMFILE * f);
158 
159 /************************************************/
160 /*   #define DEBUG      /- force statistics -/  */
161 /************************************************/
162 
163 #ifdef DEBUG
164 long tot_compressed;
165 long tot_raw;
166 long tot_cache_miss;
167 long tot_cache_hits;
168 long tot_swap_out;
169 
170 /*
171    The following pointers are here only for helping with a dumb debugger
172    that can't inspect local variables!
173  */
174 byte *decomp_wt_ptr0, *decomp_wt_limit0;
175 const byte *decomp_rd_ptr0, *decomp_rd_limit0;
176 byte *decomp_wt_ptr1, *decomp_wt_limit1;
177 const byte *decomp_rd_ptr1, *decomp_rd_limit1;
178 
179 #endif
180 
181 /* ----------------------------- Memory Allocation --------------------- */
182 void *	/* allocated memory's address, 0 if failure */
allocateWithReserve(MEMFILE * f,int sizeofBlock,int * return_code,const char * allocName,const char * errorMessage)183 allocateWithReserve(
184          MEMFILE  *f,			/* file to allocate mem to */
185          int      sizeofBlock,		/* size of block to allocate */
186          int      *return_code,         /* RET 0 ok, -ve GS-style error, or +1 if OK but low memory */
187 	 const   char     *allocName,		/* name to allocate by */
188 	 const   char     *errorMessage         /* error message to print */
189 )
190 {
191     int code = 0;	/* assume success */
192     void *block = MALLOC(f, sizeofBlock, allocName);
193 
194     if (block == NULL) {
195 	/* Try to recover block from reserve */
196 	if (sizeofBlock == sizeof(LOG_MEMFILE_BLK)) {
197 	    if (f->reserveLogBlockCount > 0) {
198 		block = f->reserveLogBlockChain;
199 		f->reserveLogBlockChain = f->reserveLogBlockChain->link;
200 		--f->reserveLogBlockCount;
201 	    }
202 	} else if (sizeofBlock == sizeof(PHYS_MEMFILE_BLK) ||
203 		   sizeofBlock == sizeof(RAW_BUFFER)
204 		   ) {
205 	    if (f->reservePhysBlockCount > 0) {
206 		block = f->reservePhysBlockChain;
207 		f->reservePhysBlockChain = f->reservePhysBlockChain->link;
208 		--f->reservePhysBlockCount;
209 	    }
210 	}
211 	if (block != NULL)
212 	    code = 1;	/* successful, but allocated from reserve */
213     }
214     if (block != NULL)
215 	f->total_space += sizeofBlock;
216     else
217 	code = gs_note_error(gs_error_VMerror);
218     *return_code = code;
219     return block;
220 }
221 
222 /* ---------------- Open/close/unlink ---------------- */
223 
224 int
memfile_fopen(char fname[gp_file_name_sizeof],const char * fmode,clist_file_ptr * pf,gs_memory_t * mem,gs_memory_t * data_mem,bool ok_to_compress)225 memfile_fopen(char fname[gp_file_name_sizeof], const char *fmode,
226 	      clist_file_ptr /*MEMFILE * */  * pf,
227 	      gs_memory_t *mem, gs_memory_t *data_mem, bool ok_to_compress)
228 {
229     MEMFILE *f = 0;
230     int code = 0;
231 
232     /* We don't implement reopening an existing file. */
233     if (fname[0] != 0 || fmode[0] != 'w') {
234 	code = gs_note_error(gs_error_invalidfileaccess);
235 	goto finish;
236     }
237 
238     /* There is no need to set fname in this implementation, */
239     /* but we do it anyway. */
240     fname[0] = (ok_to_compress ? 'a' : 'b');
241     fname[1] = 0;
242 
243     f = gs_alloc_struct(mem, MEMFILE, &st_MEMFILE,
244 			"memfile_open_scratch(MEMFILE)");
245     if (f == NULL) {
246 	eprintf1("memfile_open_scratch(%s): gs_alloc_struct failed\n", fname);
247 	code = gs_note_error(gs_error_VMerror);
248 	goto finish;
249     }
250     f->memory = mem;
251     f->data_memory = data_mem;
252     /* init an empty file, BEFORE allocating de/compress state */
253     f->compress_state = 0;	/* make clean for GC, or alloc'n failure */
254     f->decompress_state = 0;
255     f->total_space = 0;
256     f->reservePhysBlockChain = NULL;
257     f->reservePhysBlockCount = 0;
258     f->reserveLogBlockChain = NULL;
259     f->reserveLogBlockCount = 0;
260     /* init an empty file           */
261     if ((code = memfile_init_empty(f)) < 0)
262 	goto finish;
263     if ((code = memfile_set_memory_warning(f, 0)) < 0)
264 	goto finish;
265     /*
266      * Disregard the ok_to_compress flag, since the size threshold gives us
267      * a much better criterion for deciding when compression is appropriate.
268      */
269     f->ok_to_compress = /*ok_to_compress */ true;
270     f->compress_state = 0;	/* make clean for GC */
271     f->decompress_state = 0;
272     if (f->ok_to_compress) {
273 	const stream_state *compress_proto = clist_compressor_state(NULL);
274 	const stream_state *decompress_proto = clist_decompressor_state(NULL);
275 	const stream_template *compress_template = compress_proto->template;
276 	const stream_template *decompress_template = decompress_proto->template;
277 
278 	f->compress_state =
279 	    gs_alloc_struct(mem, stream_state, compress_template->stype,
280 			    "memfile_open_scratch(compress_state)");
281 	f->decompress_state =
282 	    gs_alloc_struct(mem, stream_state, decompress_template->stype,
283 			    "memfile_open_scratch(decompress_state)");
284 	if (f->compress_state == 0 || f->decompress_state == 0) {
285 	    eprintf1("memfile_open_scratch(%s): gs_alloc_struct failed\n", fname);
286 	    code = gs_note_error(gs_error_VMerror);
287 	    goto finish;
288 	}
289 	memcpy(f->compress_state, compress_proto,
290 	       gs_struct_type_size(compress_template->stype));
291 	f->compress_state->memory = mem;
292 	memcpy(f->decompress_state, decompress_proto,
293 	       gs_struct_type_size(decompress_template->stype));
294 	f->decompress_state->memory = mem;
295 	if (compress_template->set_defaults)
296 	    (*compress_template->set_defaults) (f->compress_state);
297 	if (decompress_template->set_defaults)
298 	    (*decompress_template->set_defaults) (f->decompress_state);
299     }
300     f->total_space = 0;
301 
302 #ifdef DEBUG
303     /* If this is the start, init some statistics.       */
304     /* Hack: we know the 'a' file is opened first. */
305     if (*fname == 'a') {
306 	tot_compressed = 0;
307 	tot_raw = 0;
308 	tot_cache_miss = 0;
309 	tot_cache_hits = 0;
310 	tot_swap_out = 0;
311     }
312 #endif
313 finish:
314     if (code < 0) {
315 	/* return failure, clean up memory before leaving */
316 	if (f != NULL)
317 	    memfile_fclose((clist_file_ptr)f, fname, true);
318     } else {
319       /* return success */
320       *pf = f;
321     }
322     return code;
323 }
324 
325 int
memfile_fclose(clist_file_ptr cf,const char * fname,bool delete)326 memfile_fclose(clist_file_ptr cf, const char *fname, bool delete)
327 {
328     MEMFILE *const f = (MEMFILE *)cf;
329 
330     /* We don't implement closing without deletion. */
331     if (!delete)
332 	return_error(gs_error_invalidfileaccess);
333     memfile_free_mem(f);
334 
335     /* Free reserve blocks; don't do it in memfile_free_mem because */
336     /* that routine gets called to reinit file */
337     while (f->reserveLogBlockChain != NULL) {
338 	LOG_MEMFILE_BLK *block = f->reserveLogBlockChain;
339 
340 	f->reserveLogBlockChain = block->link;
341 	FREE(f, block, "memfile_set_block_size");
342     }
343     while (f->reservePhysBlockChain != NULL) {
344 	PHYS_MEMFILE_BLK *block = f->reservePhysBlockChain;
345 
346 	f->reservePhysBlockChain = block->link;
347 	FREE(f, block, "memfile_set_block_size");
348     }
349 
350     /* deallocate de/compress state */
351     gs_free_object(f->memory, f->decompress_state,
352 		   "memfile_close_and_unlink(decompress_state)");
353     gs_free_object(f->memory, f->compress_state,
354 		   "memfile_close_and_unlink(compress_state)");
355 
356     /* deallocate the memfile object proper */
357     gs_free_object(f->memory, f, "memfile_close_and_unlink(MEMFILE)");
358     return 0;
359 }
360 
361 int
memfile_unlink(const char * fname)362 memfile_unlink(const char *fname)
363 {
364     /*
365      * Since we have no way to represent a memfile other than by the
366      * pointer, we don't (can't) implement unlinking.
367      */
368     return_error(gs_error_invalidfileaccess);
369 }
370 
371 /* ---------------- Writing ---------------- */
372 
373 /* Pre-alloc enough reserve mem blox to guarantee a write of N bytes will succeed */
374 int	/* returns 0 ok, gs_error_VMerror if insufficient */
memfile_set_memory_warning(clist_file_ptr cf,int bytes_left)375 memfile_set_memory_warning(clist_file_ptr cf, int bytes_left)
376 {
377     MEMFILE *const f = (MEMFILE *)cf;
378     int code = 0;
379     /*
380      * Determine req'd memory block count from bytes_left.
381      * Allocate enough phys & log blocks to hold bytes_left
382      * + 1 phys blk for compress_log_blk + 1 phys blk for decompress.
383      */
384     int logNeeded =
385 	(bytes_left + MEMFILE_DATA_SIZE - 1) / MEMFILE_DATA_SIZE;
386     int physNeeded = logNeeded;
387 
388     if (bytes_left > 0)
389 	++physNeeded;
390     if (f->raw_head == NULL)
391 	++physNeeded;	/* have yet to allocate read buffers */
392 
393     /* Allocate or free memory depending on need */
394     while (logNeeded > f->reserveLogBlockCount) {
395 	LOG_MEMFILE_BLK *block =
396 	    MALLOC( f, sizeof(LOG_MEMFILE_BLK), "memfile_set_block_size" );
397 
398 	if (block == NULL) {
399 	    code = gs_note_error(gs_error_VMerror);
400 	    goto finish;
401 	}
402 	block->link = f->reserveLogBlockChain;
403 	f->reserveLogBlockChain = block;
404 	++f->reserveLogBlockCount;
405     }
406     while (logNeeded < f->reserveLogBlockCount) {
407 	LOG_MEMFILE_BLK *block = f->reserveLogBlockChain;
408 
409 	f->reserveLogBlockChain = block->link;
410 	FREE(f, block, "memfile_set_block_size");
411 	--f->reserveLogBlockCount;
412     }
413     while (physNeeded > f->reservePhysBlockCount) {
414 	PHYS_MEMFILE_BLK *block =
415 	    MALLOC( f,
416 		    max( sizeof(PHYS_MEMFILE_BLK), sizeof(RAW_BUFFER) ),
417 		    "memfile_set_block_size");
418 
419 	if (block == NULL) {
420 	    code = gs_note_error(gs_error_VMerror);
421 	    goto finish;
422 	}
423 	block->link = f->reservePhysBlockChain;
424 	f->reservePhysBlockChain = block;
425 	++f->reservePhysBlockCount;
426     }
427     while (physNeeded < f->reservePhysBlockCount) {
428 	PHYS_MEMFILE_BLK *block = f->reservePhysBlockChain;
429 
430 	f->reservePhysBlockChain = block->link;
431 	FREE(f, block, "memfile_set_block_size");
432 	--f->reservePhysBlockCount;
433     }
434     f->error_code = 0;	/* memfile_set_block_size is how user resets this */
435 finish:
436     return code;
437 }
438 
439 private int
compress_log_blk(MEMFILE * f,LOG_MEMFILE_BLK * bp)440 compress_log_blk(MEMFILE * f, LOG_MEMFILE_BLK * bp)
441 {
442     int status;
443     int ecode = 0;		/* accumulate low-memory warnings */
444     int code;
445     long compressed_size;
446     byte *start_ptr;
447     PHYS_MEMFILE_BLK *newphys;
448 
449     /* compress this block */
450     f->rd.ptr = (const byte *)(bp->phys_blk->data) - 1;
451     f->rd.limit = f->rd.ptr + MEMFILE_DATA_SIZE;
452 
453     bp->phys_blk = f->phys_curr;
454     bp->phys_pdata = (char *)(f->wt.ptr) + 1;
455     if (f->compress_state->template->reinit != 0)
456 	(*f->compress_state->template->reinit)(f->compress_state);
457     compressed_size = 0;
458 
459     start_ptr = f->wt.ptr;
460     status = (*f->compress_state->template->process)(f->compress_state,
461 						     &(f->rd), &(f->wt), true);
462     bp->phys_blk->data_limit = (char *)(f->wt.ptr);
463 
464     if (status == 1) {		/* More output space needed (see strimpl.h) */
465 	/* allocate another physical block, then compress remainder       */
466 	compressed_size = f->wt.limit - start_ptr;
467 	newphys =
468 	    allocateWithReserve(f, sizeof(*newphys), &code, "memfile newphys",
469 			"compress_log_blk : MALLOC for 'newphys' failed\n");
470 	if (code < 0)
471 	    return code;
472 	ecode |= code;	/* accumulate any low-memory warnings */
473 	newphys->link = NULL;
474 	bp->phys_blk->link = newphys;
475 	f->phys_curr = newphys;
476 	f->wt.ptr = (byte *) (newphys->data) - 1;
477 	f->wt.limit = f->wt.ptr + MEMFILE_DATA_SIZE;
478 
479 	start_ptr = f->wt.ptr;
480 	status =
481 	    (*f->compress_state->template->process)(f->compress_state,
482 						    &(f->rd), &(f->wt), true);
483 	if (status != 0) {
484 	    /*
485 	     * You'd think the above line is a bug, but in real life 1 src
486 	     * block never ends up getting split across 3 dest blocks.
487 	     */
488 	    /* CHANGE memfile_set_memory_warning if this assumption changes. */
489 	    eprintf("Compression required more than one full block!\n");
490 	    return_error(gs_error_Fatal);
491 	}
492 	newphys->data_limit = (char *)(f->wt.ptr);
493     }
494     compressed_size += f->wt.ptr - start_ptr;
495     if (compressed_size > MEMFILE_DATA_SIZE) {
496 	eprintf2("\nCompression didn't - raw=%d, compressed=%ld\n",
497 		 MEMFILE_DATA_SIZE, compressed_size);
498     }
499 #ifdef DEBUG
500     tot_compressed += compressed_size;
501 #endif
502     return (status < 0 ? gs_note_error(gs_error_ioerror) : ecode);
503 }				/* end "compress_log_blk()"                                     */
504 
505 /*      Internal (private) routine to handle end of logical block       */
506 private int	/* ret 0 ok, -ve error, or +ve low-memory warning */
memfile_next_blk(MEMFILE * f)507 memfile_next_blk(MEMFILE * f)
508 {
509     LOG_MEMFILE_BLK *bp = f->log_curr_blk;
510     LOG_MEMFILE_BLK *newbp;
511     PHYS_MEMFILE_BLK *newphys, *oldphys;
512     int ecode = 0;		/* accumulate low-memory warnings */
513     int code;
514 
515     if (f->phys_curr == NULL) {	/* means NOT compressing                */
516 	/* allocate a new block                                           */
517 	newphys =
518 	    allocateWithReserve(f, sizeof(*newphys), &code, "memfile newphys",
519 			"memfile_next_blk: MALLOC 1 for 'newphys' failed\n");
520 	if (code < 0)
521 	    return code;
522 	ecode |= code;	/* accumulate low-mem warnings */
523 	newphys->link = NULL;
524 	newphys->data_limit = NULL;	/* raw                          */
525 
526 	newbp =
527 	    allocateWithReserve(f, sizeof(*newbp), &code, "memfile newbp",
528 			"memfile_next_blk: MALLOC 1 for 'newbp' failed\n");
529 	if (code < 0) {
530 	    FREE(f, newphys, "memfile newphys");
531 	    return code;
532 	}
533 	ecode |= code;	/* accumulate low-mem warnings */
534 	bp->link = newbp;
535 	newbp->link = NULL;
536 	newbp->raw_block = NULL;
537 	f->log_curr_blk = newbp;
538 
539 	/* check if need to start compressing                             */
540 	if (NEED_TO_COMPRESS(f)) {
541 	    if_debug0(':', "[:]Beginning compression\n");
542 	    /* compress the entire file up to this point                   */
543 	    if (!f->compressor_initialized) {
544 		int code = 0;
545 
546 		if (f->compress_state->template->init != 0)
547 		    code = (*f->compress_state->template->init) (f->compress_state);
548 		if (code < 0)
549 		    return_error(gs_error_VMerror);  /****** BOGUS ******/
550 		if (f->decompress_state->template->init != 0)
551 		    code = (*f->decompress_state->template->init)
552 			(f->decompress_state);
553 		if (code < 0)
554 		    return_error(gs_error_VMerror);  /****** BOGUS ******/
555 		f->compressor_initialized = true;
556 	    }
557 	    /* Write into the new physical block we just allocated,        */
558 	    /* replace it after the loop (after some blocks are freed)     */
559 	    f->phys_curr = newphys;
560 	    f->wt.ptr = (byte *) (newphys->data) - 1;
561 	    f->wt.limit = f->wt.ptr + MEMFILE_DATA_SIZE;
562 	    bp = f->log_head;
563 	    while (bp != newbp) {	/* don't compress last block    */
564 		int code;
565 
566 		oldphys = bp->phys_blk;
567 		if ((code = compress_log_blk(f, bp)) < 0)
568 		    return code;
569 		ecode |= code;
570 		FREE(f, oldphys, "memfile_next_blk(oldphys)");
571 		bp = bp->link;
572 	    }			/* end while( ) compress loop                           */
573 	    /* Allocate a physical block for this (last) logical block     */
574 	    newphys =
575 		allocateWithReserve(f, sizeof(*newphys), &code,
576 			"memfile newphys",
577 			"memfile_next_blk: MALLOC 2 for 'newphys' failed\n");
578 	    if (code < 0)
579 		return code;
580 	    ecode |= code;	/* accumulate low-mem warnings */
581 	    newphys->link = NULL;
582 	    newphys->data_limit = NULL;		/* raw                  */
583 
584 	}			/* end convert file to compressed                                 */
585 	newbp->phys_blk = newphys;
586 	f->pdata = newphys->data;
587 	f->pdata_end = newphys->data + MEMFILE_DATA_SIZE;
588     }    /* end if NOT compressing                                 */
589     /* File IS being compressed                                       */
590     else {
591 	int code;
592 
593 	oldphys = bp->phys_blk;	/* save raw phys block ID               */
594 	/* compresses bp on phys list  */
595 	if ((code = compress_log_blk(f, bp)) < 0)
596 	    return code;
597 	ecode |= code;
598 	newbp =
599 	    allocateWithReserve(f, sizeof(*newbp), &code, "memfile newbp",
600 			"memfile_next_blk: MALLOC 2 for 'newbp' failed\n");
601 	if (code < 0)
602 	    return code;
603 	ecode |= code;
604 	bp->link = newbp;
605 	newbp->link = NULL;
606 	newbp->raw_block = NULL;
607 	/* Re-use the raw phys block for this new logical blk             */
608 	newbp->phys_blk = oldphys;
609 	f->pdata = oldphys->data;
610 	f->pdata_end = f->pdata + MEMFILE_DATA_SIZE;
611 	f->log_curr_blk = newbp;
612     }				/* end else (when we are compressing)                           */
613 
614     return (ecode);
615 }
616 
617 int	/* returns # of chars actually written */
memfile_fwrite_chars(const void * data,uint len,clist_file_ptr cf)618 memfile_fwrite_chars(const void *data, uint len, clist_file_ptr cf)
619 {
620     const char *str = (const char *)data;
621     MEMFILE *f = (MEMFILE *) cf;
622     uint count = len;
623     int ecode;
624 
625     /* check if we are writing to the start of the file.  If so, then    */
626     /* free the file memory and re-initialize it (frees memory)          */
627     if (f->log_curr_pos == 0) {
628 	int code;
629 
630 	memfile_free_mem(f);
631 	if ((code = memfile_init_empty(f)) < 0) {
632 	    f->error_code = code;
633 	    return 0;
634 	}
635     }
636     if (f->log_curr_blk->link != 0) {
637 	eprintf(" Write file truncate -- need to free physical blocks.\n");
638     }
639     while (count) {
640 	uint move_count = f->pdata_end - f->pdata;
641 
642 	if (move_count == 0) {
643 	    if ((ecode = memfile_next_blk(f)) != 0) {
644 		f->error_code = ecode;
645 		if (ecode < 0)
646 		    return 0;
647 	    }
648 	} else {
649 	    if (move_count > count)
650 		move_count = count;
651 	    memmove(f->pdata, str, move_count);
652 	    f->pdata += move_count;
653 	    str += move_count;
654 	    count -= move_count;
655 	}
656     }
657     f->log_curr_pos += len;
658     f->log_length = f->log_curr_pos;	/* truncate length to here      */
659 #ifdef DEBUG
660     tot_raw += len;
661 #endif
662     return (len);
663 }
664 
665 /*                                                                      */
666 /*      Internal routine to set the f->pdata and f->pdata_end pointers  */
667 /*      for the current logical block f->log_curr_blk                   */
668 /*                                                                      */
669 /*      If data only exists in compressed form, allocate a raw buffer   */
670 /*      and decompress it.                                              */
671 /*                                                                      */
672 
673 private int
memfile_get_pdata(MEMFILE * f)674 memfile_get_pdata(MEMFILE * f)
675 {
676     int i, num_raw_buffers, status;
677     LOG_MEMFILE_BLK *bp = f->log_curr_blk;
678 
679     if (bp->phys_blk->data_limit == NULL) {
680 	/* Not compressed, return this data pointer                       */
681 	f->pdata = (bp->phys_blk)->data;
682 	i = f->log_curr_pos % MEMFILE_DATA_SIZE;	/* pos within block     */
683 	i = f->log_curr_pos - i;	/* base of block        */
684 	if (i + MEMFILE_DATA_SIZE > f->log_length)
685 	    f->pdata_end = f->pdata + f->log_length - i;
686 	else
687 	    f->pdata_end = f->pdata + MEMFILE_DATA_SIZE;
688     } else {
689 	/* data was compressed                                            */
690 	if (f->raw_head == NULL) {
691 	    /* need to allocate the raw buffer pool                        */
692 	    num_raw_buffers = GET_NUM_RAW_BUFFERS(f);
693 	    if (f->reservePhysBlockCount) {
694             /* HACK: allocate reserve block that's been reserved for
695 	     * decompression.  This buffer's block was pre-allocated to make
696 	     * sure we won't come up short here. Take from chain instead of
697 	     * allocateWithReserve() since this buf would just be wasted if
698 	     * allowed to remain preallocated. */
699 		f->raw_head = (RAW_BUFFER *)f->reservePhysBlockChain;
700 		f->reservePhysBlockChain = f->reservePhysBlockChain->link;
701 		--f->reservePhysBlockCount;
702 	    } else {
703 		int code;
704 
705 		f->raw_head =
706 		    allocateWithReserve(f, sizeof(*f->raw_head), &code,
707 					"memfile raw buffer",
708 			"memfile_get_pdata: MALLOC for 'raw_head' failed\n");
709 		if (code < 0)
710 		    return code;
711 	    }
712 	    f->raw_head->back = NULL;
713 	    f->raw_tail = f->raw_head;
714 	    f->raw_tail->log_blk = NULL;
715 	    for (i = 0; i < num_raw_buffers; i++) {
716 		f->raw_tail->fwd = (RAW_BUFFER *) MALLOC(f, sizeof(RAW_BUFFER),
717 						      "memfile raw buffer");
718 		/* if MALLOC fails, then just stop allocating            */
719 		if (!f->raw_tail->fwd)
720 		    break;
721 		f->total_space += sizeof(RAW_BUFFER);
722 		f->raw_tail->fwd->back = f->raw_tail;
723 		f->raw_tail = f->raw_tail->fwd;
724 		f->raw_tail->log_blk = NULL;
725 	    }
726 	    f->raw_tail->fwd = NULL;
727 	    num_raw_buffers = i + 1;	/* if MALLOC failed, then OK    */
728 	    if_debug1(':', "[:]Number of raw buffers allocated=%d\n",
729 		      num_raw_buffers);
730 	}			/* end allocating the raw buffer pool (first time only)           */
731 	if (bp->raw_block == NULL) {
732 #ifdef DEBUG
733 	    tot_cache_miss++;	/* count every decompress       */
734 #endif
735 	    /* find a raw buffer and decompress                            */
736 	    if (f->raw_tail->log_blk != NULL) {
737 		/* This block was in use, grab it                           */
738 #ifdef DEBUG
739 		tot_swap_out++;
740 #endif
741 		f->raw_tail->log_blk->raw_block = NULL;		/* data no longer here */
742 		f->raw_tail->log_blk = NULL;
743 	    }
744 	    /* Use the last raw block in the chain (the oldest)            */
745 	    f->raw_tail->back->fwd = NULL;	/* disconnect from tail */
746 	    f->raw_tail->fwd = f->raw_head;	/* new head             */
747 	    f->raw_head->back = f->raw_tail;
748 	    f->raw_tail = f->raw_tail->back;
749 	    f->raw_head = f->raw_head->back;
750 	    f->raw_head->back = NULL;
751 	    f->raw_head->log_blk = bp;
752 
753 	    /* Decompress the data into this raw block                     */
754 	    /* Initialize the decompressor                              */
755 	    if (f->decompress_state->template->reinit != 0)
756 		(*f->decompress_state->template->reinit) (f->decompress_state);
757 	    /* Set pointers and call the decompress routine             */
758 	    f->wt.ptr = (byte *) (f->raw_head->data) - 1;
759 	    f->wt.limit = f->wt.ptr + MEMFILE_DATA_SIZE;
760 	    f->rd.ptr = (const byte *)(bp->phys_pdata) - 1;
761 	    f->rd.limit = (const byte *)bp->phys_blk->data_limit;
762 #ifdef DEBUG
763 	    decomp_wt_ptr0 = f->wt.ptr;
764 	    decomp_wt_limit0 = f->wt.limit;
765 	    decomp_rd_ptr0 = f->rd.ptr;
766 	    decomp_rd_limit0 = f->rd.limit;
767 #endif
768 	    status = (*f->decompress_state->template->process)
769 		(f->decompress_state, &(f->rd), &(f->wt), true);
770 	    if (status == 0) {	/* More input data needed */
771 		/* switch to next block and continue decompress             */
772 		int back_up = 0;	/* adjust pointer backwards     */
773 
774 		if (f->rd.ptr != f->rd.limit) {
775 		    /* transfer remainder bytes from the previous block      */
776 		    back_up = f->rd.limit - f->rd.ptr;
777 		    for (i = 0; i < back_up; i++)
778 			*(bp->phys_blk->link->data - back_up + i) = *++f->rd.ptr;
779 		}
780 		f->rd.ptr = (const byte *)bp->phys_blk->link->data - back_up - 1;
781 		f->rd.limit = (const byte *)bp->phys_blk->link->data_limit;
782 #ifdef DEBUG
783 		decomp_wt_ptr1 = f->wt.ptr;
784 		decomp_wt_limit1 = f->wt.limit;
785 		decomp_rd_ptr1 = f->rd.ptr;
786 		decomp_rd_limit1 = f->rd.limit;
787 #endif
788 		status = (*f->decompress_state->template->process)
789 		    (f->decompress_state, &(f->rd), &(f->wt), true);
790 		if (status == 0) {
791 		    eprintf("Decompression required more than one full block!\n");
792 		    return_error(gs_error_Fatal);
793 		}
794 	    }
795 	    bp->raw_block = f->raw_head;	/* point to raw block           */
796 	}
797 	/* end if( raw_block == NULL ) meaning need to decompress data    */
798 	else {
799 	    /* data exists in the raw data cache, if not raw_head, move it */
800 	    if (bp->raw_block != f->raw_head) {
801 		/*          move to raw_head                                */
802 		/*          prev.fwd = this.fwd                             */
803 		bp->raw_block->back->fwd = bp->raw_block->fwd;
804 		if (bp->raw_block->fwd != NULL)
805 		    /*               next.back = this.back                   */
806 		    bp->raw_block->fwd->back = bp->raw_block->back;
807 		else
808 		    f->raw_tail = bp->raw_block->back;	/* tail = prev        */
809 		f->raw_head->back = bp->raw_block;	/* head.back = this     */
810 		bp->raw_block->fwd = f->raw_head;	/* this.fwd = orig head */
811 		f->raw_head = bp->raw_block;	/* head = this          */
812 		f->raw_head->back = NULL;	/* this.back = NULL     */
813 #ifdef DEBUG
814 		tot_cache_hits++;	/* counting here prevents repeats since */
815 		/* won't count if already at head       */
816 #endif
817 	    }
818 	}
819 	f->pdata = bp->raw_block->data;
820 	f->pdata_end = f->pdata + MEMFILE_DATA_SIZE;
821 	/* NOTE: last block is never compressed, so a compressed block    */
822 	/*        is always full size.                                    */
823     }				/* end else (when data was compressed)                             */
824 
825     return (0);
826 }
827 
828 /* ---------------- Reading ---------------- */
829 
830 int
memfile_fread_chars(void * data,uint len,clist_file_ptr cf)831 memfile_fread_chars(void *data, uint len, clist_file_ptr cf)
832 {
833     char *str = (char *)data;
834     MEMFILE *f = (MEMFILE *) cf;
835     uint count = len, num_read, move_count;
836 
837     num_read = f->log_length - f->log_curr_pos;
838     if (count > num_read)
839 	count = num_read;
840     num_read = count;
841 
842     while (count) {
843 	f->log_curr_pos++;	/* move into next byte */
844 	if (f->pdata == f->pdata_end) {
845 	    f->log_curr_blk = (f->log_curr_blk)->link;
846 	    memfile_get_pdata(f);
847 	}
848 	move_count = f->pdata_end - f->pdata;
849 	if (move_count > count)
850 	    move_count = count;
851 	f->log_curr_pos += move_count - 1;	/* new position         */
852 	memmove(str, f->pdata, move_count);
853 	str += move_count;
854 	f->pdata += move_count;
855 	count -= move_count;
856     }
857 
858     return (num_read);
859 }
860 
861 /* ---------------- Position/status ---------------- */
862 
863 int
memfile_ferror_code(clist_file_ptr cf)864 memfile_ferror_code(clist_file_ptr cf)
865 {
866     return (((MEMFILE *) cf)->error_code);	/* errors stored here */
867 }
868 
869 long
memfile_ftell(clist_file_ptr cf)870 memfile_ftell(clist_file_ptr cf)
871 {
872     return (((MEMFILE *) cf)->log_curr_pos);
873 }
874 
875 void
memfile_rewind(clist_file_ptr cf,bool discard_data,const char * ignore_fname)876 memfile_rewind(clist_file_ptr cf, bool discard_data, const char *ignore_fname)
877 {
878     MEMFILE *f = (MEMFILE *) cf;
879 
880     if (discard_data) {
881 	memfile_free_mem(f);
882 	/* We have to call memfile_init_empty to preserve invariants. */
883 	memfile_init_empty(f);
884     } else {
885 	f->log_curr_blk = f->log_head;
886 	f->log_curr_pos = 0;
887 	memfile_get_pdata(f);
888     }
889 }
890 
891 int
memfile_fseek(clist_file_ptr cf,long offset,int mode,const char * ignore_fname)892 memfile_fseek(clist_file_ptr cf, long offset, int mode, const char *ignore_fname)
893 {
894     MEMFILE *f = (MEMFILE *) cf;
895     long i, block_num, new_pos;
896 
897     switch (mode) {
898 	case SEEK_SET:		/* offset from the beginning of the file */
899 	    new_pos = offset;
900 	    break;
901 
902 	case SEEK_CUR:		/* offset from the current position in the file */
903 	    new_pos = offset + f->log_curr_pos;
904 	    break;
905 
906 	case SEEK_END:		/* offset back from the end of the file */
907 	    new_pos = f->log_length - offset;
908 	    break;
909 
910 	default:
911 	    return (-1);
912     }
913     if (new_pos < 0 || new_pos > f->log_length)
914 	return -1;
915     if ((f->pdata == f->pdata_end) && (f->log_curr_blk->link != NULL)) {
916 	/* log_curr_blk is actually one block behind log_curr_pos         */
917 	f->log_curr_blk = f->log_curr_blk->link;
918     }
919     block_num = new_pos / MEMFILE_DATA_SIZE;
920     i = f->log_curr_pos / MEMFILE_DATA_SIZE;
921     if (block_num < i) {	/* if moving backwards, start at beginning */
922 	f->log_curr_blk = f->log_head;
923 	i = 0;
924     }
925     for (; i < block_num; i++) {
926 	f->log_curr_blk = f->log_curr_blk->link;
927     }
928     f->log_curr_pos = new_pos;
929     memfile_get_pdata(f);	/* pointers to start of block           */
930     f->pdata += new_pos - (block_num * MEMFILE_DATA_SIZE);
931 
932     return 0;			/* return "normal" status                       */
933 }
934 
935 /* ---------------- Internal routines ---------------- */
936 
937 private void
memfile_free_mem(MEMFILE * f)938 memfile_free_mem(MEMFILE * f)
939 {
940     LOG_MEMFILE_BLK *bp, *tmpbp;
941 
942 #ifdef DEBUG
943     /* output some diagnostics about the effectiveness                   */
944     if (tot_raw > 100) {
945 	if_debug2(':', "[:]tot_raw=%ld, tot_compressed=%ld\n",
946 		  tot_raw, tot_compressed);
947     }
948     if (tot_cache_hits != 0) {
949 	if_debug3(':', "[:]Cache hits=%ld, cache misses=%ld, swapouts=%ld\n",
950 		 tot_cache_hits,
951 		 tot_cache_miss - (f->log_length / MEMFILE_DATA_SIZE),
952 		 tot_swap_out);
953     }
954     tot_raw = 0;
955     tot_compressed = 0;
956     tot_cache_hits = 0;
957     tot_cache_miss = 0;
958     tot_swap_out = 0;
959 #endif
960 
961     /* Free up memory that was allocated for the memfile              */
962     bp = f->log_head;
963 
964 /******************************************************************
965  * The following was the original algorithm here.  This algorithm has a bug:
966  * the second loop references the physical blocks again after they have been
967  * freed.
968  ******************************************************************/
969 
970 #if 0				/**************** *************** */
971 
972     if (bp != NULL) {
973 	/* Free the physical blocks that make up the compressed data      */
974 	PHYS_MEMFILE_BLK *pphys = (f->log_head)->phys_blk;
975 
976 	if (pphys->data_limit != NULL) {
977 	    /* the data was compressed, free the chain of blocks             */
978 	    while (pphys != NULL) {
979 		PHYS_MEMFILE_BLK *tmpphys = pphys->link;
980 
981 		FREE(f, pphys, "memfile_free_mem(pphys)");
982 		pphys = tmpphys;
983 	    }
984 	}
985     }
986     /* free the logical blocks                                        */
987     while (bp != NULL) {
988 	/* if this logical block was not compressed, free the phys_blk  */
989 	if (bp->phys_blk->data_limit == NULL) {
990 	    FREE(f, bp->phys_blk, "memfile_free_mem(phys_blk)");
991 	}
992 	tmpbp = bp->link;
993 	FREE(f, bp, "memfile_free_mem(log_blk)");
994 	bp = tmpbp;
995     }
996 
997 #else /**************** *************** */
998 # if 1				/**************** *************** */
999 
1000 /****************************************************************
1001  * This algorithm is correct (we think).
1002  ****************************************************************/
1003 
1004     if (bp != NULL) {
1005 	/* Null out phys_blk pointers to compressed data. */
1006 	PHYS_MEMFILE_BLK *pphys = bp->phys_blk;
1007 
1008 	{
1009 	    for (tmpbp = bp; tmpbp != NULL; tmpbp = tmpbp->link)
1010 		if (tmpbp->phys_blk->data_limit != NULL)
1011 		    tmpbp->phys_blk = 0;
1012 	}
1013 	/* Free the physical blocks that make up the compressed data      */
1014 	if (pphys->data_limit != NULL) {
1015 	    /* the data was compressed, free the chain of blocks             */
1016 	    while (pphys != NULL) {
1017 		PHYS_MEMFILE_BLK *tmpphys = pphys->link;
1018 
1019 		FREE(f, pphys, "memfile_free_mem(pphys)");
1020 		pphys = tmpphys;
1021 	    }
1022 	}
1023     }
1024     /* Now free the logical blocks, and any uncompressed physical blocks. */
1025     while (bp != NULL) {
1026 	if (bp->phys_blk != NULL) {
1027 	    FREE(f, bp->phys_blk, "memfile_free_mem(phys_blk)");
1028 	}
1029 	tmpbp = bp->link;
1030 	FREE(f, bp, "memfile_free_mem(log_blk)");
1031 	bp = tmpbp;
1032     }
1033 
1034 /***********************************************************************
1035  * This algorithm appears to be both simpler and free of the bug that
1036  * occasionally causes the older one to reference freed blocks; but in
1037  * fact it can miss blocks, because the very last compressed logical block
1038  * can have spill into a second physical block, which is not referenced by
1039  * any logical block.
1040  ***********************************************************************/
1041 
1042 # else				/**************** *************** */
1043 
1044     {
1045 	PHYS_MEMFILE_BLK *prev_phys = 0;
1046 
1047 	while (bp != NULL) {
1048 	    PHYS_MEMFILE_BLK *phys = bp->phys_blk;
1049 
1050 	    if (phys != prev_phys) {
1051 		FREE(f, phys, "memfile_free_mem(phys_blk)");
1052 		prev_phys = phys;
1053 	    }
1054 	    tmpbp = bp->link;
1055 	    FREE(f, bp, "memfile_free_mem(log_blk)");
1056 	    bp = tmpbp;
1057 	}
1058     }
1059 
1060 # endif				/**************** *************** */
1061 #endif /**************** *************** */
1062 
1063     f->log_head = NULL;
1064 
1065     /* Free any internal compressor state. */
1066     if (f->compressor_initialized) {
1067 	if (f->decompress_state->template->release != 0)
1068 	    (*f->decompress_state->template->release) (f->decompress_state);
1069 	if (f->compress_state->template->release != 0)
1070 	    (*f->compress_state->template->release) (f->compress_state);
1071 	f->compressor_initialized = false;
1072     }
1073     /* free the raw buffers                                           */
1074     while (f->raw_head != NULL) {
1075 	RAW_BUFFER *tmpraw = f->raw_head->fwd;
1076 
1077 	FREE(f, f->raw_head, "memfile_free_mem(raw)");
1078 	f->raw_head = tmpraw;
1079     }
1080 }
1081 
1082 private int
memfile_init_empty(MEMFILE * f)1083 memfile_init_empty(MEMFILE * f)
1084 {
1085     PHYS_MEMFILE_BLK *pphys;
1086     LOG_MEMFILE_BLK *plog;
1087 
1088    /* Zero out key fields so that allocation failure will be unwindable */
1089     f->phys_curr = NULL; 	/* flag as file not compressed    	*/
1090     f->log_head = NULL;
1091     f->log_curr_blk = NULL;
1092     f->log_curr_pos = 0;
1093     f->log_length = 0;
1094     f->raw_head = NULL;
1095     f->compressor_initialized = false;
1096     f->total_space = 0;
1097 
1098     /* File empty - get a physical mem block (includes the buffer area)  */
1099     pphys = MALLOC(f, sizeof(*pphys), "memfile pphys");
1100     if (!pphys) {
1101 	eprintf("memfile_init_empty: MALLOC for 'pphys' failed\n");
1102 	return_error(gs_error_VMerror);
1103     }
1104     f->total_space += sizeof(*pphys);
1105     pphys->data_limit = NULL;	/* raw data for now     */
1106 
1107    /* Get logical mem block to go with physical one */
1108     plog = (LOG_MEMFILE_BLK *)MALLOC( f, sizeof(*plog), "memfile_init_empty" );
1109     if (plog == NULL) {
1110 	FREE(f, pphys, "memfile_init_empty");
1111 	eprintf("memfile_init_empty: MALLOC for log_curr_blk failed\n");
1112 	return_error(gs_error_VMerror);
1113     }
1114     f->total_space += sizeof(*plog);
1115     f->log_head = f->log_curr_blk = plog;
1116     f->log_curr_blk->link = NULL;
1117     f->log_curr_blk->phys_blk = pphys;
1118     f->log_curr_blk->phys_pdata = NULL;
1119     f->log_curr_blk->raw_block = NULL;
1120 
1121     f->pdata = pphys->data;
1122     f->pdata_end = f->pdata + MEMFILE_DATA_SIZE;
1123 
1124     f->error_code = 0;
1125 
1126     return 0;
1127 }
1128