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