diff options
Diffstat (limited to 'base/gxclfile.c')
-rw-r--r-- | base/gxclfile.c | 575 |
1 files changed, 575 insertions, 0 deletions
diff --git a/base/gxclfile.c b/base/gxclfile.c new file mode 100644 index 00000000..d2887dd2 --- /dev/null +++ b/base/gxclfile.c @@ -0,0 +1,575 @@ +/* Copyright (C) 2001-2019 Artifex Software, Inc. + All Rights Reserved. + + This software is provided AS-IS with no warranty, either express or + implied. + + This software is distributed under license and may not be copied, + modified or distributed except as expressly authorized under the terms + of the license contained in the file LICENSE in this distribution. + + Refer to licensing information at http://www.artifex.com or contact + Artifex Software, Inc., 1305 Grant Avenue - Suite 200, Novato, + CA 94945, U.S.A., +1(415)492-9861, for further information. +*/ + + +/* File-based command list implementation */ +#include "assert_.h" +#include "stdio_.h" +#include "string_.h" +#include "unistd_.h" +#include "gserrors.h" +#include "gsmemory.h" +#include "gp.h" +#include "gxclio.h" + +#include "valgrind.h" + + +/* This is an implementation of the command list I/O interface */ +/* that uses the file system for storage. */ + +/* clist cache code so that wrapped files don't incur a performance penalty */ +#define CL_CACHE_NSLOTS (3) +#define CL_CACHE_SLOT_SIZE_LOG2 (15) +#define CL_CACHE_SLOT_EMPTY (-1) + +static clist_io_procs_t clist_io_procs_file; + +typedef struct +{ + int64_t blocknum; + byte *base; +} CL_CACHE_SLOT; + +typedef struct +{ + int block_size; /* full block size, MUST BE power of 2 */ + int nslots; + int64_t filesize; + gs_memory_t *memory; /* save our allocator */ + CL_CACHE_SLOT *slots; /* array of slots */ + byte *base; /* save base of slot data area */ +} CL_CACHE; + +/* Forward references */ +CL_CACHE *cl_cache_alloc(gs_memory_t *mem); +void cl_cache_destroy(CL_CACHE *cache); +CL_CACHE *cl_cache_read_init(CL_CACHE *cache, int nslots, int64_t block_size, int64_t filesize); +int cl_cache_read(byte *data, int len, int64_t pos, CL_CACHE *cache); +CL_CACHE_SLOT * cl_cache_get_empty_slot(CL_CACHE *cache, int64_t pos); +void cl_cache_load_slot(CL_CACHE *cache, CL_CACHE_SLOT *slot, int64_t pos, byte *data, int len); + +#define CL_CACHE_NEEDS_INIT(cache) (cache != NULL && cache->filesize == 0) + +CL_CACHE * +cl_cache_alloc(gs_memory_t *mem) +{ + CL_CACHE *cache; + + /* allocate and initialilze the cache to filesize = 0 to signal read_init needed */ + cache = (CL_CACHE *)gs_alloc_bytes(mem, sizeof(CL_CACHE), "alloc CL_CACHE"); + if (cache != NULL) { + cache->filesize = 0; + cache->nslots = 0; + cache->block_size = 0; + cache->slots = NULL; + cache->base = NULL; + cache->memory = mem; + } + return cache; +} + +void +cl_cache_destroy(CL_CACHE *cache) +{ + if (cache == NULL) + return; + + if (cache->slots != NULL) { + gs_free_object(cache->memory, cache->base, "CL_CACHE SLOT data"); + gs_free_object(cache->memory, cache->slots, "CL_CACHE slots array"); + } + gs_free_object(cache->memory, cache, "CL_CACHE for IFILE"); +} + +/* Set the cache up for reading. The filesize is used for EOF */ +CL_CACHE * +cl_cache_read_init(CL_CACHE *cache, int nslots, int64_t block_size, int64_t filesize) +{ + /* NB: if fail, and cache is still NULL, proceed without cache, reading will cope */ + if (cache == NULL || cache->filesize != 0) + return cache; /* once we've done the init, filesize will be set */ + + if ((filesize+block_size)/block_size < nslots) + nslots = (filesize + block_size)/block_size; /* limit at blocks needed for entire file */ + cache->slots = (CL_CACHE_SLOT *)gs_alloc_bytes(cache->memory, nslots * sizeof(CL_CACHE_SLOT), + "CL_CACHE slots array"); + if (cache->slots == NULL) { + gs_free_object(cache->memory, cache, "Free CL_CACHE for IFILE"); + cache = NULL; /* cache not possible */ + } else { + cache->slots[0].base = (byte *)gs_alloc_bytes(cache->memory, nslots * block_size, + "CL_CACHE_SLOT data"); + if (cache->slots[0].base == NULL) { + gs_free_object(cache->memory, cache->slots, "Free CL_CACHE for IFILE"); + gs_free_object(cache->memory, cache, "Free CL_CACHE for IFILE"); + cache = NULL; /* cache not possible */ + } else { + /* success, initialize the slots */ + int i; + + for (i=0; i < nslots; i++) { + cache->slots[i].blocknum = CL_CACHE_SLOT_EMPTY; + cache->slots[i].base = cache->slots[0].base + (i * block_size); + } + cache->base = cache->slots[0].base; /* save for the 'destroy' (slots array moves around) */ + cache->nslots = nslots; + cache->block_size = block_size; + cache->filesize = filesize; + } + } + return cache; /* May be NULL. If so, no cache used */ +} + +/* Find the cache for the slot containing the 'pos'. */ +/* return the number of bytes read, up to 'len' bytes */ +/* returns 0 if 'pos' not in cache, -1 if pos at or past EOF. */ +int +cl_cache_read(byte *data, int len, int64_t pos, CL_CACHE *cache) +{ + int nread = 0; + int slot; + int offset; + int64_t blocknum = pos / cache->block_size; + + if (pos >= cache->filesize) + return -1; + + /* find the slot */ + for (slot = 0; slot < cache->nslots; slot++) { + if (blocknum == cache->slots[slot].blocknum) + break; + } + if (slot >= cache->nslots) + return 0; /* block not in cache */ + + if (slot != 0) { + /* move the slot we found to the top, moving the rest down */ + byte *base = cache->slots[slot].base; + int i; + + for (i = slot; i > 0; i--) { + cache->slots[i].base = cache->slots[i-1].base; + cache->slots[i].blocknum = cache->slots[i-1].blocknum; + } + cache->slots[0].blocknum = blocknum; + cache->slots[0].base = base; + } + offset = pos - cache->slots[0].blocknum * cache->block_size; + nread = min(cache->block_size - offset, len); + if (nread + pos > cache->filesize) + nread = cache->filesize - pos; /* limit for EOF */ + memcpy(data, cache->slots[0].base + offset, nread); + return nread; +} + +/* 'pos' not used yet */ +/* discard the LRU, move remaining slots down and return the first as new MRU */ +CL_CACHE_SLOT * +cl_cache_get_empty_slot(CL_CACHE *cache, int64_t pos) +{ + /* the LRU is in the last slot, so re-use it */ + CL_CACHE_SLOT *pslot = &(cache->slots[0]); /* slot used will always be first, possibly after moving */ + int64_t slot0_blocknum = pslot->blocknum; + + if (slot0_blocknum == CL_CACHE_SLOT_EMPTY) + return pslot; + + /* if more than on slot in the cache, handle moving slots to bump the LRU (last) */ + /* If the block at slot 0 hasn't been flushed at least once before, just use slot 0 */ + if (cache->nslots > 1) { + /* rotate the cache to re-use the last slot (LRU) and move it to the top, moving the rest down */ + byte *last_slot_base = cache->slots[cache->nslots - 1].base; /* save the base for the last slot */ + int i; + + /* move the rest down */ + for (i=cache->nslots - 1; i > 0; i--) { + cache->slots[i].blocknum = cache->slots[i-1].blocknum; + cache->slots[i].base = cache->slots[i-1].base; + } + pslot->base = last_slot_base; + } + pslot->blocknum = CL_CACHE_SLOT_EMPTY; + return pslot; +} + +void +cl_cache_load_slot(CL_CACHE *cache, CL_CACHE_SLOT *slot, int64_t pos, byte *data, int len) +{ + slot->blocknum = pos / cache->block_size; + memmove(slot->base, data, len); +} + +/* Use our own FILE structure so that, on some platforms, we write and read + * tmp files via a single file descriptor. That allows cleaning of tmp files + * to be addressed via DELETE_ON_CLOSE under Windows, and immediate unlink + * after opening under Linux. When running in this mode, we keep our own + * record of position within the file for the sake of thread safety + */ + +#define ENC_FILE_STR ("encoded_file_ptr_%p") +#define ENC_FILE_STRX ("encoded_file_ptr_0x%p") + +typedef struct +{ + gs_memory_t *mem; + gp_file *f; + int64_t pos; + int64_t filesize; /* filesize maintained by clist_fwrite */ + CL_CACHE *cache; +} IFILE; + +static void +file_to_fake_path(clist_file_ptr file, char fname[gp_file_name_sizeof]) +{ + gs_sprintf(fname, ENC_FILE_STR, file); +} + +static clist_file_ptr +fake_path_to_file(const char *fname) +{ + clist_file_ptr i1, i2; + + int r1 = sscanf(fname, ENC_FILE_STR, &i1); + int r2 = sscanf(fname, ENC_FILE_STRX, &i2); + return r2 == 1 ? i2 : (r1 == 1 ? i1 : NULL); +} + +static IFILE *wrap_file(gs_memory_t *mem, gp_file *f, const char *fmode) +{ + IFILE *ifile; + + if (!f) return NULL; + ifile = (IFILE *)gs_alloc_bytes(mem->non_gc_memory, sizeof(*ifile), "Allocate wrapped IFILE"); + if (!ifile) { + gp_fclose(f); + return NULL; + } + ifile->mem = mem->non_gc_memory; + ifile->f = f; + ifile->pos = 0; + ifile->filesize = 0; + ifile->cache = cl_cache_alloc(ifile->mem); + return ifile; +} + +static int clist_close_file(IFILE *ifile) +{ + int res = 0; + if (ifile) { + if (ifile->f != NULL) + res = gp_fclose(ifile->f); + if (ifile->cache != NULL) + cl_cache_destroy(ifile->cache); + gs_free_object(ifile->mem, ifile, "Free wrapped IFILE"); + } + return res; +} + +/* ------ Open/close/unlink ------ */ + +static int +clist_fopen(char fname[gp_file_name_sizeof], const char *fmode, + clist_file_ptr * pcf, gs_memory_t * mem, gs_memory_t *data_mem, + bool ok_to_compress) +{ + if (*fname == 0) { + if (fmode[0] == 'r') + return_error(gs_error_invalidfileaccess); + if (gp_can_share_fdesc()) { + *pcf = (clist_file_ptr)wrap_file(mem, gp_open_scratch_file_rm(mem, + gp_scratch_file_name_prefix, + fname, fmode), fmode); + /* If the platform supports FILE duplication then we overwrite the + * file name with an encoded form of the FILE pointer */ + if (*pcf != NULL) + file_to_fake_path(*pcf, fname); + } else { + *pcf = (clist_file_ptr)wrap_file(mem, gp_open_scratch_file(mem, + gp_scratch_file_name_prefix, + fname, fmode), fmode); + } + } else { + clist_file_ptr ocf = fake_path_to_file(fname); + if (ocf) { + /* A special (fake) fname is passed in. If so, clone the FILE handle */ + *pcf = wrap_file(mem, gp_fdup(((IFILE *)ocf)->f, fmode), fmode); + /* when cloning, copy other parts not done by wrap_file */ + if (*pcf) + ((IFILE *)(*pcf))->filesize = ((IFILE *)ocf)->filesize; + } else { + *pcf = wrap_file(mem, gp_fopen(mem, fname, fmode), fmode); + } + } + + if (*pcf == NULL) { + emprintf1(mem, "Could not open the scratch file %s.\n", fname); + return_error(gs_error_invalidfileaccess); + } + + return 0; +} + +static int +clist_unlink(const char *fname) +{ + clist_file_ptr ocf = fake_path_to_file(fname); + if (ocf) { + /* fname is an encoded file pointer. The file will either have been + * created with the delete-on-close option, or already have been + * unlinked. We need only close the FILE */ + return clist_close_file((IFILE *)ocf) != 0 ? gs_note_error(gs_error_ioerror) : 0; + } else { + return (unlink(fname) != 0 ? gs_note_error(gs_error_ioerror) : 0); + } +} + +static int +clist_fclose(clist_file_ptr cf, const char *fname, bool delete) +{ + clist_file_ptr ocf = fake_path_to_file(fname); + if (ocf == cf) { + /* fname is an encoded file pointer, and cf is the FILE used to create it. + * We shouldn't close it unless we have been asked to delete it, in which + * case closing it will delete it */ + return delete ? (clist_close_file((IFILE *)ocf) ? gs_note_error(gs_error_ioerror) : 0) : 0; + } else { + return (clist_close_file((IFILE *) cf) != 0 ? gs_note_error(gs_error_ioerror) : + delete ? clist_unlink(fname) : + 0); + } +} + +/* ------ Writing ------ */ + +static int +clist_fwrite_chars(const void *data, uint len, clist_file_ptr cf) +{ + int res = 0; + IFILE *icf = (IFILE *)cf; + + if (gp_can_share_fdesc()) { + res = gp_fpwrite((char *)data, len, ((IFILE *)cf)->pos, ((IFILE *)cf)->f); + } else { + res = gp_fwrite(data, 1, len, ((IFILE *)cf)->f); + } + if (res >= 0) + icf->pos += len; + icf->filesize = icf->pos; /* write truncates file */ + if (!CL_CACHE_NEEDS_INIT(icf->cache)) { + /* writing invalidates the read cache */ + cl_cache_destroy(icf->cache); + icf->cache = NULL; + } + return res; +} + +/* ------ Reading ------ */ + +static int +clist_fread_chars(void *data, uint len, clist_file_ptr cf) +{ + int nread = 0; + + if (gp_can_share_fdesc()) { + IFILE *icf = (IFILE *)cf; + byte *dp = data; + + /* if we have a cache, check if it needs init, and do it */ + if (CL_CACHE_NEEDS_INIT(icf->cache)) { + icf->cache = cl_cache_read_init(icf->cache, CL_CACHE_NSLOTS, 1<<CL_CACHE_SLOT_SIZE_LOG2, icf->filesize); + } + /* cl_cache_read_init may have failed, and set cache to NULL, check before using it */ + if (icf->cache != NULL) { + do { + int n; + + if ((n = cl_cache_read(dp, len-nread, icf->pos+nread, icf->cache)) < 0) + break; + if (n == 0) { + /* pos was not in cache, get a slot and load it, then loop */ + CL_CACHE_SLOT *slot = cl_cache_get_empty_slot(icf->cache, icf->pos+nread); /* cannot fail */ + int64_t block_pos = (icf->pos+nread) & ~(icf->cache->block_size - 1); + int fill_len = gp_fpread((char *)(slot->base), icf->cache->block_size, block_pos, icf->f); + + cl_cache_load_slot(icf->cache, slot, block_pos, slot->base, fill_len); + } + nread += n; + dp += n; + } while (nread < len); + } else { + /* no cache -- just do the read */ + nread = gp_fpread(data, len, icf->pos, icf->f); + } + if (nread >= 0) + icf->pos += nread; + } else { + gp_file *f = ((IFILE *)cf)->f; + byte *str = data; + + /* The typical implementation of fread */ + /* is extremely inefficient for small counts, */ + /* so we just use straight-line code instead. */ + switch (len) { + default: + return gp_fread(str, 1, len, f); + case 8: + *str++ = (byte) gp_fgetc(f); + case 7: + *str++ = (byte) gp_fgetc(f); + case 6: + *str++ = (byte) gp_fgetc(f); + case 5: + *str++ = (byte) gp_fgetc(f); + case 4: + *str++ = (byte) gp_fgetc(f); + case 3: + *str++ = (byte) gp_fgetc(f); + case 2: + *str++ = (byte) gp_fgetc(f); + case 1: + *str = (byte) gp_fgetc(f); + } + nread = len; + } + return nread; +} + +/* ------ Position/status ------ */ + +static int +clist_set_memory_warning(clist_file_ptr cf, int bytes_left) +{ + return 0; /* no-op */ +} + +static int +clist_ferror_code(clist_file_ptr cf) +{ + return (gp_ferror(((IFILE *)cf)->f) ? gs_error_ioerror : 0); +} + +static int64_t +clist_ftell(clist_file_ptr cf) +{ + IFILE *ifile = (IFILE *)cf; + + return gp_can_share_fdesc() ? ifile->pos : gp_ftell(ifile->f); +} + +static int +clist_rewind(clist_file_ptr cf, bool discard_data, const char *fname) +{ + gp_file *f = ((IFILE *)cf)->f; + IFILE *ocf = fake_path_to_file(fname); + char fmode[4]; + + strcpy(fmode, "w+"); + strcat(fmode, gp_fmode_binary_suffix); + + if (ocf) { + if (discard_data) { + /* fname is an encoded ifile pointer. We can use an entirely + * new scratch file. */ + char tfname[gp_file_name_sizeof] = {0}; + const gs_memory_t *mem = ocf->f->memory; + gp_fclose(ocf->f); + ocf->f = gp_open_scratch_file_rm(mem, gp_scratch_file_name_prefix, tfname, fmode); + if (ocf->f == NULL) + return_error(gs_error_ioerror); + /* if there was a cache, get rid of it an get a new (empty) one */ + /* When we start reading, we will allocate a cache based on the filesize */ + if (ocf->cache != NULL) { + cl_cache_destroy(ocf->cache); + ocf->cache = cl_cache_alloc(ocf->mem); + if (ocf->cache == NULL) + return_error(gs_error_ioerror); + } + ((IFILE *)cf)->filesize = 0; + } + ((IFILE *)cf)->pos = 0; + } else { + if (discard_data) { + /* + * The ANSI C stdio specification provides no operation for + * truncating a file at a given position, or even just for + * deleting its contents; we have to use a bizarre workaround to + * get the same effect. + */ + + /* Opening with "w" mode deletes the contents when closing. */ + f = gp_freopen(fname, gp_fmode_wb, f); + if (f == NULL) return_error(gs_error_ioerror); + ((IFILE *)cf)->f = gp_freopen(fname, fmode, f); + if (((IFILE *)cf)->f == NULL) return_error(gs_error_ioerror); + ((IFILE *)cf)->pos = 0; + ((IFILE *)cf)->filesize = 0; + } else { + gp_rewind(f); + } + } + return 0; +} + +static int +clist_fseek(clist_file_ptr cf, int64_t offset, int mode, const char *ignore_fname) +{ + IFILE *ifile = (IFILE *)cf; + int res = 0; + + if (!gp_can_share_fdesc()) { + res = gp_fseek(ifile->f, offset, mode); + } + /* NB: if gp_can_share_fdesc, we don't actually seek */ + if (res >= 0) { + /* Update the ifile->pos */ + switch (mode) { + case SEEK_SET: + ifile->pos = offset; + break; + case SEEK_CUR: + ifile->pos += offset; + break; + case SEEK_END: + ifile->pos = ifile->filesize; /* filesize maintained in clist_fwrite */ + break; + } + } + return res; +} + +static clist_io_procs_t clist_io_procs_file = { + clist_fopen, + clist_fclose, + clist_unlink, + clist_fwrite_chars, + clist_fread_chars, + clist_set_memory_warning, + clist_ferror_code, + clist_ftell, + clist_rewind, + clist_fseek, +}; + +init_proc(gs_gxclfile_init); +int +gs_gxclfile_init(gs_memory_t *mem) +{ +#ifdef PACIFY_VALGRIND + VALGRIND_HG_DISABLE_CHECKING(&clist_io_procs_file_global, sizeof(clist_io_procs_file_global)); +#endif + clist_io_procs_file_global = &clist_io_procs_file; + return 0; +} |