Subversion Repositories planix.SVN

Rev

Rev 2 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
2 - 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 */
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 */
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 *
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 */
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 */
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 */
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
 
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
 
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 */
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
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
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 */
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
 
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
}