xref: /plan9/sys/src/cmd/gs/src/gp_unix_cache.c (revision 593dc095aefb2a85c828727bbfa9da139a49bdf4)
1 /* Copyright (C) 2003-2004 artofcode LLC. 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: gp_unix_cache.c,v 1.3 2004/07/14 15:34:25 giles Exp $ */
18 /* Generic POSIX persistent-cache implementation for Ghostscript */
19 
20 #include "stdio_.h"
21 #include "string_.h"
22 #include "time_.h"
23 #include <stdlib.h> /* should use gs_malloc() instead */
24 #include "gconfigd.h"
25 #include "gp.h"
26 #include "md5.h"
27 
28 /* ------ Persistent data cache types ------*/
29 
30 #define GP_CACHE_VERSION 0
31 
32 /* cache entry type */
33 typedef struct gp_cache_entry_s {
34     int type;
35     int keylen;
36     byte *key;
37     md5_byte_t hash[16];
38     char *filename;
39     int len;
40     void *buffer;
41     int dirty;
42     time_t last_used;
43 } gp_cache_entry;
44 
45 /* initialize a new gp_cache_entry struct */
gp_cache_clear_entry(gp_cache_entry * item)46 private void gp_cache_clear_entry(gp_cache_entry *item)
47 {
48     item->type = -1;
49     item->key = NULL;
50     item->keylen = 0;
51     item->filename = NULL;
52     item->buffer = NULL;
53     item->len = 0;
54     item->dirty = 0;
55     item->last_used = 0;
56 }
57 
58 /* get the cache directory's path */
gp_cache_prefix(void)59 private char *gp_cache_prefix(void)
60 {
61     char *prefix = NULL;
62     int plen = 0;
63 
64     /* get the cache directory path */
65     if (gp_getenv("GS_CACHE_DIR", (char *)NULL, &plen) < 0) {
66         prefix = malloc(plen);
67         gp_getenv("GS_CACHE_DIR", prefix, &plen);
68         plen--;
69     } else {
70 #ifdef GS_CACHE_DIR
71         prefix = strdup(GS_CACHE_DIR);
72 #else
73         prefix = strdup(".cache");
74 #endif
75         plen = strlen(prefix);
76     }
77 
78     /* substitute $HOME for '~' */
79     if (plen > 1 && prefix[0] == '~') {
80         char *home, *path;
81         int hlen = 0;
82 	unsigned int pathlen = 0;
83         gp_file_name_combine_result result;
84 
85         if (gp_getenv("HOME", (char *)NULL, &hlen) < 0) {
86             home = malloc(hlen);
87             if (home == NULL) return prefix;
88             gp_getenv("HOME", home, &hlen);
89             hlen--;
90             if (plen == 1) {
91                 /* only "~" */
92                 free(prefix);
93                 return home;
94             }
95             /* substitue for the initial '~' */
96             pathlen = hlen + plen + 1;
97             path = malloc(pathlen);
98             if (path == NULL) { free(home); return prefix; }
99             result = gp_file_name_combine(home, hlen, prefix+2, plen-2, false, path, &pathlen);
100             if (result == gp_combine_success) {
101                 free(prefix);
102                 prefix = path;
103             } else {
104                 dlprintf1("file_name_combine failed with code %d\n", result);
105             }
106             free(home);
107         }
108     }
109 #ifdef DEBUG_CACHE
110     dlprintf1("cache dir read as '%s'\n", prefix);
111 #endif
112     return prefix;
113 }
114 
115 /* compute the cache index file's path */
116 private char *
gp_cache_indexfilename(const char * prefix)117 gp_cache_indexfilename(const char *prefix)
118 {
119     const char *fn = "gs_cache";
120     char *path;
121     unsigned int len;
122     gp_file_name_combine_result result;
123 
124     len = strlen(prefix) + strlen(fn) + 2;
125     path = malloc(len);
126 
127     result = gp_file_name_combine(prefix, strlen(prefix), fn, strlen(fn), true, path, &len);
128     if (result == gp_combine_small_buffer) {
129         /* handle the case when the combination requires more than one extra character */
130         free(path);
131         path = malloc(++len);
132         result = gp_file_name_combine(prefix, strlen(prefix), fn, strlen(fn), true, path, &len);
133     }
134     if (result != gp_combine_success) {
135         dlprintf1("pcache: file_name_combine for indexfilename failed with code %d\n", result);
136         free(path);
137         return NULL;
138     }
139     return path;
140 }
141 
142 /* compute and set a cache key's hash */
gp_cache_hash(gp_cache_entry * entry)143 private void gp_cache_hash(gp_cache_entry *entry)
144 {
145     md5_state_t md5;
146 
147     /* we use md5 hashes of the key */
148     md5_init(&md5);
149     md5_append(&md5, entry->key, entry->keylen);
150     md5_finish(&md5, entry->hash);
151 }
152 
153 /* compute and set cache item's filename */
gp_cache_filename(const char * prefix,gp_cache_entry * item)154 private void gp_cache_filename(const char *prefix, gp_cache_entry *item)
155 {
156     const char hexmap[16] = {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};
157     char *fn = malloc(gp_file_name_sizeof), *fni;
158     int i;
159 
160     fni = fn;
161     *fni++ = hexmap[item->type>>4 & 0x0F];
162     *fni++ = hexmap[item->type & 0x0F];
163     *fni++ = '.';
164     for (i = 0; i < 16; i++) {
165         *fni++ = hexmap[(item->hash[i]>>4 & 0x0F)];
166         *fni++ = hexmap[(item->hash[i] & 0x0F)];
167     }
168     *fni = '\0';
169 
170     if (item->filename) free(item->filename);
171     item->filename = fn;
172 }
173 
174 /* generate an access path for a cache item */
gp_cache_itempath(const char * prefix,gp_cache_entry * item)175 private char *gp_cache_itempath(const char *prefix, gp_cache_entry *item)
176 {
177     const char *fn = item->filename;
178     gp_file_name_combine_result result;
179     char *path;
180     unsigned int len;
181 
182     len = strlen(prefix) + strlen(fn) + 2;
183     path = malloc(len);
184     result = gp_file_name_combine(prefix, strlen(prefix),
185         fn, strlen(fn), false, path, &len);
186 
187     if (result != gp_combine_success) {
188         dlprintf1("pcache: file_name_combine failed on cache item filename with code %d\n", result);
189     }
190 
191     return path;
192 }
193 
gp_cache_saveitem(FILE * file,gp_cache_entry * item)194 private int gp_cache_saveitem(FILE *file, gp_cache_entry* item)
195 {
196     unsigned char version = 0;
197     int ret;
198 
199 #ifdef DEBUG_CACHE
200     dlprintf2("pcache: saving key with version %d, data length %d\n", version, item->len);
201 #endif
202     ret = fwrite(&version, 1, 1, file);
203     ret = fwrite(&(item->keylen), 1, sizeof(item->keylen), file);
204     ret = fwrite(item->key, 1, item->keylen, file);
205     ret = fwrite(&(item->len), 1, sizeof(item->len), file);
206     ret = fwrite(item->buffer, 1, item->len, file);
207     item->dirty = 0;
208     return ret;
209 }
210 
gp_cache_loaditem(FILE * file,gp_cache_entry * item,gp_cache_alloc alloc,void * userdata)211 private int gp_cache_loaditem(FILE *file, gp_cache_entry *item, gp_cache_alloc alloc, void *userdata)
212 {
213     unsigned char version;
214     unsigned char *filekey = NULL;
215     int len, keylen;
216 
217     fread(&version, 1, 1, file);
218     if (version != GP_CACHE_VERSION) {
219 #ifdef DEBUG_CACHE
220         dlprintf2("pcache file version mismatch (%d vs expected %d)\n", version, GP_CACHE_VERSION);
221 #endif
222         return -1;
223     }
224     fread(&keylen, 1, sizeof(keylen), file);
225     if (keylen != item->keylen) {
226 #ifdef DEBUG_CACHE
227         dlprintf2("pcache file has correct hash but wrong key length (%d vs %d)\n",
228             keylen, item->keylen);
229 #endif
230         return -1;
231     }
232     filekey = malloc(keylen);
233     if (filekey != NULL)
234         fread(filekey, 1, keylen, file);
235     if (memcmp(filekey, item->key, keylen)) {
236 #ifdef DEBUG_CACHE
237         dlprintf("pcache file has correct hash but doesn't match the full key\n");
238 #endif
239         free(filekey);
240         item->buffer = NULL;
241         item->len = 0;
242         return -1;
243     }
244     free(filekey);
245 
246     fread(&len, 1, sizeof(len), file);
247 #ifdef DEBUG_CACHE
248     dlprintf2("key matches file with version %d, data length %d\n", version, len);
249 #endif
250     item->buffer = alloc(userdata, len);
251     if (item->buffer == NULL) {
252         dlprintf("pcache: unable to allocate buffer for file data!\n");
253         return -1;
254     }
255 
256     item->len = fread(item->buffer, 1, len, file);
257     item->dirty = 1;
258     item->last_used = time(NULL);
259 
260     return 0;
261 }
262 
263 /* convert a two-character hex string to an integer */
readhexbyte(const char * s)264 private int readhexbyte(const char *s)
265 {
266     const char hexmap[16] = {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};
267     int i,r;
268 
269     for (i = 0; i < 16; i++)
270         if (hexmap[i] == *s) break;
271     if (i == 16) return -1;
272     r = i;
273     s++;
274     for (i = 0; i < 16; i++)
275         if (hexmap[i] == *s) break;
276     if (i == 16) return -1;
277     r = r<<4 | i;
278     return r;
279 }
280 
281 private int
gp_cache_read_entry(FILE * file,gp_cache_entry * item)282 gp_cache_read_entry(FILE *file, gp_cache_entry *item)
283 {
284     char line[256];
285     char fn[32];
286     int i;
287 
288     if (!fgets(line, 256, file)) return -1;
289 
290     /* skip comments */
291     if (line[0] == '#') return 1;
292 
293     /* otherwise, parse the line */
294     sscanf(line, "%s %ld\n", fn, &item->last_used);
295     /* unpack the type from the filename */
296     item->type = readhexbyte(fn);
297     /* unpack the md5 hash from the filename */
298     for (i = 0; i < 16; i++)
299         item->hash[i] = readhexbyte(fn + 3 + 2*i);
300     /* remember the filename */
301     if (item->filename) free(item->filename);
302     item->filename = malloc(strlen(fn) + 1);
303     memcpy(item->filename, fn, strlen(fn));
304     /* null other fields */
305     item->key = NULL;
306     item->keylen = 0;
307     item->len = 0;
308     item->buffer = NULL;
309 
310     return 0;
311 }
312 
313 private int
gp_cache_write_entry(FILE * file,gp_cache_entry * item)314 gp_cache_write_entry(FILE *file, gp_cache_entry *item)
315 {
316     fprintf(file, "%s %ld\n", item->filename, item->last_used);
317     return 0;
318 }
319 
320 
321 /* insert a buffer under a (type, key) pair */
gp_cache_insert(int type,byte * key,int keylen,void * buffer,int buflen)322 int gp_cache_insert(int type, byte *key, int keylen, void *buffer, int buflen)
323 {
324     char *prefix, *path;
325     char *infn,*outfn;
326     FILE *file, *in, *out;
327     gp_cache_entry item, item2;
328     int code, hit = 0;
329 
330     prefix = gp_cache_prefix();
331     infn = gp_cache_indexfilename(prefix);
332     {
333         int len = strlen(infn) + 2;
334         outfn = malloc(len);
335         memcpy(outfn, infn, len - 2);
336         outfn[len-2] = '+';
337         outfn[len-1] = '\0';
338     }
339 
340     in = fopen(infn, "r");
341     if (in == NULL) {
342         dlprintf1("pcache: unable to open '%s'\n", infn);
343         return -1;
344     }
345     out = fopen(outfn, "w");
346     if (out == NULL) {
347         dlprintf1("pcache: unable to open '%s'\n", outfn);
348         return -1;
349     }
350 
351     fprintf(out, "# Ghostscript persistent cache index table\n");
352 
353     /* construct our cache item */
354     gp_cache_clear_entry(&item);
355     item.type = type;
356     item.key = key;
357     item.keylen = keylen;
358     item.buffer = buffer;
359     item.len = buflen;
360     item.dirty = 1;
361     item.last_used = time(NULL);
362     gp_cache_hash(&item);
363     gp_cache_filename(prefix, &item);
364 
365     /* save it to disk */
366     path = gp_cache_itempath(prefix, &item);
367     file = fopen(path, "wb");
368     if (file != NULL) {
369         gp_cache_saveitem(file, &item);
370         fclose(file);
371     }
372 
373     /* now loop through the index to update or insert the entry */
374     gp_cache_clear_entry(&item2);
375     while ((code = gp_cache_read_entry(in, &item2)) >= 0) {
376         if (code == 1) continue;
377         if (!memcmp(item.hash, item2.hash, 16)) {
378             /* replace the matching line */
379             gp_cache_write_entry(out, &item);
380             hit = 1;
381         } else {
382             /* copy out the previous line */
383             gp_cache_write_entry(out, &item2);
384         }
385     }
386     if (!hit) {
387         /* there was no matching line */
388         gp_cache_write_entry(out, &item);
389     }
390     free(item.filename);
391     fclose(out);
392     fclose(in);
393 
394     /* replace the cache index with our new version */
395     unlink(infn);
396     rename(outfn,infn);
397 
398     free(prefix);
399     free(infn);
400     free(outfn);
401 
402     return 0;
403 }
404 
gp_cache_query(int type,byte * key,int keylen,void ** buffer,gp_cache_alloc alloc,void * userdata)405 int gp_cache_query(int type, byte* key, int keylen, void **buffer,
406     gp_cache_alloc alloc, void *userdata)
407 {
408     char *prefix, *path;
409     char *infn,*outfn;
410     FILE *file, *in, *out;
411     gp_cache_entry item, item2;
412     int code, hit = 0;
413 
414     prefix = gp_cache_prefix();
415     infn = gp_cache_indexfilename(prefix);
416     {
417         int len = strlen(infn) + 2;
418         outfn = malloc(len);
419         memcpy(outfn, infn, len - 2);
420         outfn[len-2] = '+';
421         outfn[len-1] = '\0';
422     }
423 
424     in = fopen(infn, "r");
425     if (in == NULL) {
426         dlprintf1("pcache: unable to open '%s'\n", infn);
427         return -1;
428     }
429     out = fopen(outfn, "w");
430     if (out == NULL) {
431         dlprintf1("pcache: unable to open '%s'\n", outfn);
432         return -1;
433     }
434 
435     fprintf(out, "# Ghostscript persistent cache index table\n");
436 
437     /* construct our query */
438     gp_cache_clear_entry(&item);
439     item.type = type;
440     item.key = key;
441     item.keylen = keylen;
442     item.last_used = time(NULL);
443     gp_cache_hash(&item);
444     gp_cache_filename(prefix, &item);
445 
446     /* look for it on the disk */
447     path = gp_cache_itempath(prefix, &item);
448     file = fopen(path, "rb");
449     if (file != NULL) {
450         hit = gp_cache_loaditem(file, &item, alloc, userdata);
451         fclose(file);
452     } else {
453         hit = -1;
454     }
455 
456     gp_cache_clear_entry(&item2);
457     while ((code = gp_cache_read_entry(in, &item2)) >= 0) {
458         if (code == 1) continue;
459         if (!hit && !memcmp(item.hash, item2.hash, 16)) {
460             /* replace the matching line */
461             gp_cache_write_entry(out, &item);
462             item.dirty = 0;
463         } else {
464             /* copy out the previous line */
465             gp_cache_write_entry(out, &item2);
466         }
467     }
468     if (item.dirty) {
469         /* there was no matching line -- shouldn't happen */
470         gp_cache_write_entry(out, &item);
471     }
472     free(item.filename);
473     fclose(out);
474     fclose(in);
475 
476     /* replace the cache index with our new version */
477     unlink(infn);
478     rename(outfn,infn);
479 
480     free(prefix);
481     free(infn);
482     free(outfn);
483 
484     if (!hit) {
485         *buffer = item.buffer;
486         return item.len;
487     } else {
488         *buffer = NULL;
489         return -1;
490     }
491 }
492