// Start of cache.h #define CACHE_HASH_SIZE 8 // In 32-bit words. struct cache_hash { uint32_t hash[CACHE_HASH_SIZE]; }; // Initialise a blank cache. static void cache_hash_init(struct cache_hash *c); // Hash some bytes and add them to the accumulated hash. static void cache_hash(struct cache_hash *out, const char *in, size_t n); // Try to restore cache contents from a file with the given name. // Assumes the cache is invalid if it contains the given hash. // Allocates memory and reads the cache conents, which is returned in // *buf with size *buflen. If the cache is successfully loaded, this // function returns 0. Otherwise it returns nonzero. Errno is set if // the failure to load the cache is due to anything except invalid // cache conents. Note that failing to restore the cache is not // necessarily a problem: it might just be invalid or not created yet. static int cache_restore(const char *fname, const struct cache_hash *hash, unsigned char **buf, size_t *buflen); // Store cache contents in the given file, with the given hash. static int cache_store(const char *fname, const struct cache_hash *hash, const unsigned char *buf, size_t buflen); // Now for the implementation. static void cache_hash_init(struct cache_hash *c) { memset(c->hash, 0, CACHE_HASH_SIZE * sizeof(uint32_t)); } static void cache_hash(struct cache_hash *out, const char *in, size_t n) { // Adaptation of djb2 for larger output size by storing intermediate // states. uint32_t hash = 5381; for (int i = 0; i < n; i++) { hash = ((hash << 5) + hash) + in[i]; out->hash[i % CACHE_HASH_SIZE] ^= hash; } } #define CACHE_HEADER_SIZE 8 static const char cache_header[CACHE_HEADER_SIZE] = "FUTHARK\0"; static int cache_restore(const char *fname, const struct cache_hash *hash, unsigned char **buf, size_t *buflen) { FILE *f = fopen(fname, "rb"); if (f == NULL) { return 1; } char f_header[CACHE_HEADER_SIZE]; if (fread(f_header, sizeof(char), CACHE_HEADER_SIZE, f) != CACHE_HEADER_SIZE) { goto error; } if (memcmp(f_header, cache_header, CACHE_HEADER_SIZE) != 0) { goto error; } if (fseek(f, 0, SEEK_END) != 0) { goto error; } int64_t f_size = (int64_t)ftell(f); if (fseek(f, CACHE_HEADER_SIZE, SEEK_SET) != 0) { goto error; } int64_t expected_size; if (fread(&expected_size, sizeof(int64_t), 1, f) != 1) { goto error; } if (f_size != expected_size) { errno = 0; goto error; } int32_t f_hash[CACHE_HASH_SIZE]; if (fread(f_hash, sizeof(int32_t), CACHE_HASH_SIZE, f) != CACHE_HASH_SIZE) { goto error; } if (memcmp(f_hash, hash->hash, CACHE_HASH_SIZE) != 0) { errno = 0; goto error; } *buflen = f_size - CACHE_HEADER_SIZE - sizeof(int64_t) - CACHE_HASH_SIZE*sizeof(int32_t); *buf = malloc(*buflen); if (fread(*buf, sizeof(char), *buflen, f) != *buflen) { free(*buf); goto error; } fclose(f); return 0; error: fclose(f); return 1; } static int cache_store(const char *fname, const struct cache_hash *hash, const unsigned char *buf, size_t buflen) { FILE *f = fopen(fname, "wb"); if (f == NULL) { return 1; } if (fwrite(cache_header, CACHE_HEADER_SIZE, 1, f) != 1) { goto error; } int64_t size = CACHE_HEADER_SIZE + sizeof(int64_t) + CACHE_HASH_SIZE*sizeof(int32_t) + buflen; if (fwrite(&size, sizeof(size), 1, f) != 1) { goto error; } if (fwrite(hash->hash, sizeof(int32_t), CACHE_HASH_SIZE, f) != CACHE_HASH_SIZE) { goto error; } if (fwrite(buf, sizeof(unsigned char), buflen, f) != buflen) { goto error; } fclose(f); return 0; error: fclose(f); return 1; } // End of cache.h