123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354 |
- #include "platform.h"
- #include <sys/stat.h>
- #include <assert.h>
- #include <errno.h>
- #include <stdint.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <unistd.h>
- #include "asprintf.h"
- #include "ccache_internal.h"
- #include "dirutil.h"
- #include "multitape_internal.h"
- #include "patricia.h"
- #include "sysendian.h"
- #include "warnp.h"
- #include "ccache.h"
- /* Cookie structure passed to callback_count and callback_write_*. */
- struct ccache_write_internal {
- size_t N; /* Number of records. */
- char * s; /* File name. */
- FILE * f; /* File handle. */
- char * sbuf; /* Contains a NUL-terminated entry path. */
- size_t sbuflen; /* Allocation size of sbuf. */
- };
- static int callback_count(void * cookie, uint8_t * s, size_t slen,
- void * rec);
- static int callback_write_rec(void * cookie, uint8_t * s, size_t slen,
- void * rec);
- static int callback_write_data(void * cookie, uint8_t * s, size_t slen,
- void * rec);
- /* Should we skip this record? */
- static int
- skiprecord(struct ccache_record * ccr)
- {
- /*
- * Don't write an entry if there are no chunks and no trailer; if
- * there's no data, we don't accomplish anything by having a record
- * of the file in our cache.
- */
- if ((ccr->nch == 0) && (ccr->tlen == 0))
- return (1);
- /*
- * Don't write an entry if it hasn't been used recently; people often
- * run several sets of archives covering different directories, so we
- * don't want to drop cache entries as soon as they're not used in an
- * archive, but we don't want to keep them for too long either so that
- * we don't waste time / memory / disk space keeping track of a file
- * which we'll never archive again.
- */
- if (ccr->age > MAXAGE)
- return (1);
- /*
- * Don't write an entry if it has negative mtime. It is very unlikely
- * to be correct, and if something is mangling a file's modification
- * time there's too much of a risk that we'd rely on the modification
- * time and incorrectly conclude that it hasn't been modified since
- * the last time we looked at it.
- */
- if (ccr->mtime < 0)
- return (1);
- /* This record looks reasonable; write it out. */
- return (0);
- }
- /* Callback to count the number of records which will be written. */
- static int
- callback_count(void * cookie, uint8_t * s, size_t slen, void * rec)
- {
- struct ccache_write_internal * W = cookie;
- struct ccache_record * ccr = rec;
- (void)s; /* UNUSED */
- (void)slen; /* UNUSED */
- /* Count records we're not skipping. */
- if (!skiprecord(ccr))
- W->N += 1;
- /* Success! */
- return (0);
- }
- /* Callback to write a record and path suffix to disk. */
- static int
- callback_write_rec(void * cookie, uint8_t * s, size_t slen, void * rec)
- {
- struct ccache_record_external ccre;
- struct ccache_write_internal * W = cookie;
- struct ccache_record * ccr = rec;
- size_t plen;
- /* Skip records which we don't want stored. */
- if (skiprecord(ccr))
- goto done;
- /* Sanity checks. */
- assert(slen <= UINT32_MAX);
- assert((ccr->size >= 0) && ((uintmax_t)ccr->size <= UINT64_MAX));
- assert((uintmax_t)ccr->mtime <= UINT64_MAX);
- assert((uintmax_t)ccr->ino <= UINT64_MAX);
- /* Figure out how much prefix is shared. */
- for (plen = 0; plen < slen && plen < W->sbuflen; plen++) {
- if (s[plen] != W->sbuf[plen])
- break;
- }
- /* Convert integers to portable format. */
- le64enc(ccre.ino, (uint64_t)ccr->ino);
- le64enc(ccre.size, (uint64_t)ccr->size);
- le64enc(ccre.mtime, (uint64_t)ccr->mtime);
- le64enc(ccre.nch, (uint64_t)ccr->nch);
- le32enc(ccre.tlen, (uint32_t)ccr->tlen);
- le32enc(ccre.tzlen, (uint32_t)ccr->tzlen);
- le32enc(ccre.prefixlen, (uint32_t)plen);
- le32enc(ccre.suffixlen, (uint32_t)(slen - plen));
- le32enc(ccre.age, (uint32_t)(ccr->age + 1));
- /* Write cache entry header to disk. */
- if (fwrite(&ccre, sizeof(ccre), 1, W->f) != 1)
- goto err0;
- /* Write path suffix to disk. */
- if (fwrite(s + plen, slen - plen, 1, W->f) != 1)
- goto err0;
- /* Enlarge last-path buffer if needed. */
- if (W->sbuflen < slen + 1) {
- free(W->sbuf);
- W->sbuflen = slen + 1;
- if ((W->sbuf = malloc(W->sbuflen)) == NULL) {
- W->sbuflen = 0;
- goto err0;
- }
- memcpy(W->sbuf, s, slen);
- } else
- memcpy(W->sbuf + plen, s + plen, slen - plen);
- W->sbuf[slen] = 0;
- done:
- /* Success! */
- return (0);
- err0:
- /* Failure! */
- return (-1);
- }
- /* Callback to write chunk headers and compressed entry trailers to disk. */
- static int
- callback_write_data(void * cookie, uint8_t * s, size_t slen, void * rec)
- {
- struct ccache_write_internal * W = cookie;
- struct ccache_record * ccr = rec;
- (void)s; /* UNUSED */
- (void)slen; /* UNUSED */
- /* Skip records which we don't want stored. */
- if (skiprecord(ccr))
- goto done;
- /* Write chunkheader records to disk, if any. */
- if (ccr->chp != NULL) {
- if (fwrite(ccr->chp, sizeof(struct chunkheader),
- ccr->nch, W->f) != ccr->nch)
- goto err0;
- }
- /* Write compressed trailer to disk, if any. */
- if (ccr->ztrailer != NULL) {
- if (fwrite(ccr->ztrailer, ccr->tzlen, 1, W->f) != 1)
- goto err0;
- }
- done:
- /* Success! */
- return (0);
- err0:
- /* Failure! */
- return (-1);
- }
- /**
- * ccache_write(cache, path):
- * Write the given chunkification cache into the directory ${path}.
- */
- int
- ccache_write(CCACHE * cache, const char * path)
- {
- struct ccache_internal * C = cache;
- struct ccache_write_internal W;
- uint8_t N[4];
- char * s_old;
- /* The caller must pass a file name to be written. */
- assert(path != NULL);
- /* Construct name of temporary cache file. */
- if (asprintf(&W.s, "%s/cache.new", path) == -1) {
- warnp("asprintf");
- goto err0;
- }
- /* Open the cache file for writing. */
- if ((W.f = fopen(W.s, "w")) == NULL) {
- warnp("fopen(%s)", W.s);
- goto err1;
- }
- /*-
- * We make three passes through the cache tree:
- * 1. Counting the number of records which will be written to disk.
- * This is necessary since records in the cache which are too old
- * will not be written, but the on-disk cache format starts with
- * the number of records.
- * 2. Writing the records and suffixes.
- * 3. Writing the cached chunk headers and compressed entry trailers.
- */
- /* Count the number of records which need to be written. */
- W.N = 0;
- if (patricia_foreach(C->tree, callback_count, &W)) {
- warnp("patricia_foreach");
- goto err2;
- }
- /* Check that we don't have too many cache records. */
- if (W.N > UINT32_MAX) {
- warn0("Programmer error: "
- "The cache cannot contain more than 2^32-1 entries");
- goto err2;
- }
- /* Write the number of records to the file. */
- le32enc(N, (uint32_t)W.N);
- if (fwrite(N, 4, 1, W.f) != 1) {
- warnp("fwrite(%s)", W.s);
- goto err2;
- }
- /* Write the records and suffixes. */
- W.sbuf = NULL;
- W.sbuflen = 0;
- if (patricia_foreach(C->tree, callback_write_rec, &W)) {
- warnp("Error writing cache to %s", W.s);
- goto err2;
- }
- free(W.sbuf);
- /* Write the chunk headers and compressed entry trailers. */
- if (patricia_foreach(C->tree, callback_write_data, &W)) {
- warnp("Error writing cache to %s", W.s);
- goto err2;
- }
- /* Finish writing the file. */
- if (dirutil_fsync(W.f, W.s))
- goto err2;
- /* Close the file. */
- if (fclose(W.f))
- warnp("fclose");
- /* Construct the name of the old cache file. */
- if (asprintf(&s_old, "%s/cache", path) == -1) {
- warnp("asprintf");
- goto err1;
- }
- /* Delete the old file, if it exists. */
- if (unlink(s_old)) {
- if (errno != ENOENT) {
- warnp("unlink(%s)", s_old);
- free(s_old);
- goto err1;
- }
- }
- /* Move the new cache file into place. */
- if (rename(W.s, s_old)) {
- warnp("rename(%s, %s)", W.s, s_old);
- free(s_old);
- goto err1;
- }
- /* Free strings allocated by asprintf. */
- free(s_old);
- free(W.s);
- /* Success! */
- return (0);
- err2:
- if (fclose(W.f))
- warnp("fclose");
- err1:
- free(W.s);
- err0:
- /* Failure! */
- return (-1);
- }
- /**
- * ccache_remove(path):
- * Delete the chunkification cache from the directory ${path}.
- */
- int
- ccache_remove(const char * path)
- {
- char * s;
- /* The caller must pass a file name to be deleted. */
- assert(path != NULL);
- /* Construct the name of the cache file. */
- if (asprintf(&s, "%s/cache", path) == -1) {
- warnp("asprintf");
- goto err1;
- }
- /* Delete the file if it exists. */
- if (unlink(s)) {
- if (errno != ENOENT) {
- warnp("unlink(%s)", s);
- goto err1;
- }
- }
- /* Free string allocated by asprintf. */
- free(s);
- /* Success! */
- return (0);
- err1:
- free(s);
- /* Failure! */
- return (-1);
- }
|