123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667 |
- #include "platform.h"
- #include <sys/stat.h>
- #include <errno.h>
- #include <stdint.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <unistd.h>
- #include <zlib.h>
- #include "ccache_internal.h"
- #include "crypto.h"
- #include "multitape.h"
- #include "multitape_internal.h"
- #include "patricia.h"
- #include "sysendian.h"
- #include "tsnetwork.h"
- #include "warnp.h"
- #include "ccache.h"
- /* A cookie for higher layers to access a cache entry. */
- struct ccache_entry {
- struct ccache_internal * cci; /* Cache data structure. */
- struct ccache_record * ccr; /* Actual cache entry. */
- struct ccache_record ** ccrp; /* Pointer to pointer in tree. */
- int hittrailer; /* Non-zero if the multitape layer */
- /* has told us about a trailer. */
- uint8_t * trailer; /* Uncompressed trailer. */
- ino_t ino_new; /* New inode number. */
- off_t size_new; /* New file size. */
- time_t mtime_new; /* New modification time. */
- };
- static int callback_addchunk(void *, struct chunkheader *);
- static int callback_addtrailer(void *, const uint8_t *, size_t);
- static int callback_faketrailer(void *, const uint8_t *, size_t);
- /* Callback to add a chunk header to a cache entry. */
- static int
- callback_addchunk(void * cookie, struct chunkheader * ch)
- {
- struct ccache_entry * cce = cookie;
- struct ccache_record * ccr = cce->ccr;
- struct chunkheader * p;
- size_t nchalloc_new;
- /*
- * Has the multitape layer written a "trailer" already for this file?
- * If so, return without doing anything. This can occur if an archive
- * checkpoint occurs in the middle of an archive entry's data.
- */
- if (cce->hittrailer)
- goto done;
- /* Do we need to expand the allocated space? */
- if (ccr->nch >= ccr->nchalloc) {
- /* Double the allocated memory. */
- if (ccr->nchalloc)
- nchalloc_new = ccr->nchalloc * 2;
- else {
- /* No data or mmapped data. */
- nchalloc_new = ccr->nch + 1;
- }
- /* Make sure we don't overflow. */
- if (nchalloc_new >
- SIZE_MAX / sizeof(struct chunkheader)) {
- errno = ENOMEM;
- goto err0;
- }
- /* Attempt to reallocate. */
- if (ccr->nchalloc) {
- if ((p = realloc(ccr->chp, nchalloc_new *
- sizeof(struct chunkheader))) == NULL)
- goto err0;
- } else {
- if ((p = malloc(nchalloc_new *
- sizeof(struct chunkheader))) == NULL)
- goto err0;
- memcpy(p, ccr->chp, ccr->nch *
- sizeof(struct chunkheader));
- }
- /* Successfully reallocated. */
- ccr->chp = p;
- ccr->nchalloc = nchalloc_new;
- }
- /* We now have space; add the new record. */
- memcpy(ccr->chp + ccr->nch, ch, sizeof(struct chunkheader));
- ccr->nch += 1;
- /* Adjust memory usage accounting. */
- cce->cci->chunksusage += sizeof(struct chunkheader);
- done:
- /* Success! */
- return (0);
- err0:
- /* Failure! */
- return (-1);
- }
- /* Callback to add a file trailer. */
- static int
- callback_addtrailer(void * cookie, const uint8_t * buf, size_t buflen)
- {
- struct ccache_entry * cce = cookie;
- struct ccache_record * ccr = cce->ccr;
- uint8_t * zbuf;
- uLongf zlen;
- int rc;
- /*
- * Has the multitape layer written a "trailer" already for this file?
- * If so, return without doing anything. This can occur if an archive
- * checkpoint occurs in the middle of an archive entry's data.
- */
- if (cce->hittrailer)
- goto done;
- /* We have now been informed about a trailer. */
- cce->hittrailer = 1;
- /* Allocate space for the trailer. */
- zlen = buflen + (buflen >> 9) + 13;
- if ((zbuf = malloc(zlen)) == NULL)
- goto err0;
- /* Compress trailer. */
- if ((rc = compress2(zbuf, &zlen, buf, buflen, 9)) != Z_OK) {
- switch (rc) {
- case Z_MEM_ERROR:
- errno = ENOMEM;
- warnp("Error compressing data");
- break;
- case Z_BUF_ERROR:
- warn0("Programmer error: "
- "Buffer too small to hold zlib-compressed data");
- break;
- default:
- warn0("Programmer error: "
- "Unexpected error code from compress2: %d", rc);
- break;
- }
- goto err1;
- }
- /* Reallocate to correct length. */
- if ((ccr->ztrailer = realloc(zbuf, zlen)) == NULL)
- goto err1;
- ccr->tlen = buflen;
- ccr->tzlen = zlen;
- ccr->flags = ccr->flags | CCR_ZTRAILER_MALLOC;
- /* Adjust memory usage accounting. */
- cce->cci->trailerusage += zlen;
- done:
- /* Success! */
- return (0);
- err1:
- free(zbuf);
- err0:
- /* Failure! */
- return (-1);
- }
- /*
- * Callback to record that the multitape code has logged a trailer but not
- * bother caching the trailer.
- */
- static int
- callback_faketrailer(void * cookie, const uint8_t * buf, size_t buflen)
- {
- struct ccache_entry * cce = cookie;
- (void)buf; /* UNUSED */
- (void)buflen; /* UNUSED */
- /* We have now been informed about a trailer. */
- cce->hittrailer = 1;
- /* Success! */
- return (0);
- }
- /**
- * ccache_entry_lookup(cache, path, sb, cookie, fullentry):
- * An archive entry is being written for the file ${path} with lstat data
- * ${sb}, to the multitape with write cookie ${cookie}. Look up the file in
- * the chunkification cache ${cache}, and set ${fullentry} to a non-zero
- * value iff the cache can provide at least sb->st_size bytes of the archive
- * entry. Return a cookie which can be passed to either ccache_entry_write
- * or ccache_entry_start depending upon whether ${fullentry} is zero or not.
- */
- CCACHE_ENTRY *
- ccache_entry_lookup(CCACHE * cache, const char * path, const struct stat * sb,
- TAPE_W * cookie, int * fullentry)
- {
- struct ccache_internal * C = cache;
- struct ccache_entry * cce;
- int fresh;
- size_t cnum = 0; /* No chunks known to be available yet. */
- off_t skiplen = 0; /* No data known to be providable yet. */
- ssize_t lenwrit;
- uLongf tbuflen;
- int rc;
- /* Allocate memory. */
- if ((cce = malloc(sizeof(struct ccache_entry))) == NULL)
- goto err0;
- /* Record the cache with which this entry is affiliated. */
- cce->cci = cache;
- /*
- * The multitape layer hasn't written any "trailer" for this file
- * yet. It doesn't matter if we have a trailer in our cache -- if
- * we're in a position where we're getting callbacks from the
- * multitape code (i.e., if we can't supply the entire file using
- * cached data) we'll have thrown away the trailer we have.
- */
- cce->hittrailer = 0;
- /* Record the new inode number, size, and modification time. */
- cce->ino_new = sb->st_ino;
- cce->size_new = sb->st_size;
- cce->mtime_new = sb->st_mtime;
- /* Look up cache entry. */
- if ((cce->ccrp = (struct ccache_record **)patricia_lookup(C->tree,
- (const uint8_t *)path, strlen(path))) == NULL) {
- /* No cache entry for this path. Create an empty record. */
- if ((cce->ccr = malloc(sizeof(struct ccache_record))) == NULL)
- goto err1;
- memset(cce->ccr, 0, sizeof(struct ccache_record));
- /* No decompressed trailer. */
- cce->trailer = NULL;
- /* That's all, folks! */
- goto done;
- }
- /* Entry is in the tree. */
- cce->ccr = *cce->ccrp;
- /* Is the cache entry fresh? */
- if ((cce->ino_new == cce->ccr->ino) &&
- (cce->size_new == cce->ccr->size) &&
- (cce->mtime_new == cce->ccr->mtime))
- fresh = 1;
- else
- fresh = 0;
- /* If the cache entry is fresh, check if the chunks are available. */
- if (fresh) {
- /* Check if chunks are still available. */
- for (; cnum < cce->ccr->nch; cnum++) {
- lenwrit = writetape_ischunkpresent(cookie,
- cce->ccr->chp + cnum);
- /* Error? */
- if (lenwrit < 0)
- goto err1;
- /* Not present? */
- if (lenwrit == 0)
- break;
- /* We can supply this data. */
- skiplen += lenwrit;
- }
- }
- /*
- * If the cache entry is fresh; all of the chunks are available; we
- * have a trailer; and the trailer is long enough that having it will
- * allow us to provide the entire archive entry out of the cache;
- * then try to decompress the trailer.
- */
- if (fresh &&
- (cnum == cce->ccr->nch) &&
- (cce->ccr->tlen > 0) &&
- (skiplen + (off_t)(cce->ccr->tlen) >= sb->st_size)) {
- /* Allocate space for trailer. */
- tbuflen = cce->ccr->tlen;
- if ((cce->trailer = malloc(tbuflen)) == NULL)
- goto err1;
- /* Decompress trailer. */
- rc = uncompress(cce->trailer, &tbuflen,
- cce->ccr->ztrailer, cce->ccr->tzlen);
- /* Print warnings. */
- if (rc != Z_OK) {
- switch (rc) {
- case Z_MEM_ERROR:
- errno = ENOMEM;
- warnp("Error decompressing cache");
- break;
- case Z_BUF_ERROR:
- case Z_DATA_ERROR:
- warn0("Warning: cached trailer is corrupt");
- break;
- default:
- warn0("Programmer error: "
- "Unexpected error code from "
- "uncompress: %d", rc);
- break;
- }
- } else if (tbuflen != cce->ccr->tlen) {
- warn0("Cached trailer is corrupt");
- rc = Z_DATA_ERROR;
- }
- /* If the trailer didn't decompress properly, clean it up. */
- if (rc != Z_OK) {
- free(cce->trailer);
- cce->trailer = NULL;
- }
- /* We can supply the trailer data from the cache. */
- skiplen += cce->ccr->tlen;
- } else {
- cce->trailer = NULL;
- }
- /*
- * If there is a compressed trailer but no decompressed trailer, we
- * must have decided that the compressed trailer was useless; delete
- * it.
- */
- if ((cce->trailer == NULL) && (cce->ccr->tlen > 0)) {
- /* Free the compressed trailer if appropriate. */
- if (cce->ccr->flags & CCR_ZTRAILER_MALLOC) {
- cce->cci->trailerusage -= cce->ccr->tzlen;
- free(cce->ccr->ztrailer);
- }
- /* We have no compressed trailer. */
- cce->ccr->ztrailer = NULL;
- cce->ccr->tlen = cce->ccr->tzlen = 0;
- }
- done:
- /* Can we supply the entire file worth of data? */
- if (skiplen >= sb->st_size)
- *fullentry = 1;
- else
- *fullentry = 0;
- /* Success! */
- return (cce);
- err1:
- free(cce);
- err0:
- /* Failure! */
- return (NULL);
- }
- /**
- * ccache_entry_write(cce, cookie):
- * Write the cached archive entry ${cce} to the multitape with write cookie
- * ${cookie}. Note that this may only be called if ${cce} was returned by
- * a ccache_entry_lookup which set ${fullentry} to a non-zero value. Return
- * the length written.
- */
- off_t
- ccache_entry_write(CCACHE_ENTRY * cce, TAPE_W * cookie)
- {
- off_t skiplen = 0;
- size_t cnum;
- ssize_t lenwrit;
- /* Make sure the cache entry isn't stale. */
- if ((cce->ino_new != cce->ccr->ino) ||
- (cce->size_new != cce->ccr->size) ||
- (cce->mtime_new != cce->ccr->mtime)) {
- warn0("Programmer error: "
- "ccache_entry_write called with stale cache entry");
- goto err0;
- }
- /* Write chunks. */
- for (cnum = 0; cnum < cce->ccr->nch; cnum++) {
- lenwrit = writetape_writechunk(cookie, cce->ccr->chp + cnum);
- /* Error? */
- if (lenwrit < 0)
- goto err0;
- /* We should always be able to write chunks at this point. */
- if (lenwrit == 0) {
- warn0("Programmer error: "
- "writetape_writechunk unexpectedly returned 0");
- goto err0;
- }
- skiplen += lenwrit;
- }
- /* If we have a trailer, write it. */
- if (cce->trailer != NULL) {
- lenwrit = writetape_write(cookie, cce->trailer,
- cce->ccr->tlen);
- /* Error? */
- if (lenwrit < 0)
- goto err0;
- skiplen += lenwrit;
- }
- /* Success! */
- return (skiplen);
- err0:
- /* Failure! */
- return (-1);
- }
- /**
- * ccache_entry_writefile(cce, cookie, notrailer, fd):
- * Write data from the file descriptor ${fd} to the multitape with write
- * cookie ${cookie}, using the cache entry ${cce} as a hint about how data
- * is chunkified; and set up callbacks from the multitape layer so that the
- * cache entry will be updated with any further chunks and (if ${notrailer}
- * is zero) any trailer. Return the length written.
- */
- off_t
- ccache_entry_writefile(CCACHE_ENTRY * cce, TAPE_W * cookie,
- int notrailer, int fd)
- {
- off_t skiplen = 0;
- uint8_t * chunkbuf;
- size_t chunklen, cpos;
- size_t cnum;
- ssize_t lenwrit, lenread;
- uint8_t hbuf[32];
- /*
- * Make sure there is no trailer in this cache entry -- a trailer
- * should only exist if we can supply the entire file, in which case
- * ccache_entry_write should be called instead.
- */
- if (cce->ccr->tlen > 0) {
- warn0("Programmer error: "
- "ccache_entry_writefile called but trailer exists");
- goto err0;
- }
- /* If we have some chunks, allocate a buffer for verification. */
- if (cce->ccr->nch) {
- if ((chunkbuf = malloc(MAXCHUNK)) == NULL)
- goto err0;
- } else {
- chunkbuf = NULL;
- }
- /* Read chunk-sized blocks and write them if unchanged. */
- for (cnum = 0; cnum < cce->ccr->nch; cnum++) {
- /* Handle network activity if necessary. */
- if (network_select(0))
- goto err1;
- /* Decode a chunk. */
- chunklen = le32dec((cce->ccr->chp + cnum)->len);
- /* Sanity check. */
- if (chunklen > MAXCHUNK) {
- warn0("Cache entry is corrupt");
- break;
- }
- /*
- * We can't go beyond the length which libarchive thinks the
- * file is, even if the file has grown since when we called
- * lstat on it and the cache is corrupt.
- */
- if ((skiplen + (off_t)chunklen) > cce->size_new)
- break;
- /* Read until we've got the whole chunk. */
- for (cpos = 0; cpos < chunklen; cpos += (size_t)lenread) {
- lenread = read(fd, chunkbuf + cpos, chunklen - cpos);
- if (lenread < 0) {
- warnp("reading file");
- goto err1;
- } else if (lenread == 0) {
- /*
- * There's nothing wrong with the file being
- * shorter than it used to be.
- */
- break;
- }
- }
- /* If we hit EOF, we can't use this chunk. */
- if (cpos < chunklen)
- break;
- /* Compute the hash of the data we've read. */
- if (crypto_hash_data(CRYPTO_KEY_HMAC_CHUNK,
- chunkbuf, chunklen, hbuf))
- goto err1;
- /* Is it different? */
- if (memcmp(hbuf, (cce->ccr->chp + cnum)->hash, 32))
- break;
- /* Ok, pass the chunk header to the multitape code. */
- lenwrit = writetape_writechunk(cookie, cce->ccr->chp + cnum);
- /* Error? */
- if (lenwrit < 0)
- goto err1;
- /*
- * Chunk not present? This can happen in here, since
- * we don't verify that all the chunks are available
- * during ccache_entry_start if the file has changed.
- */
- if (lenwrit == 0)
- break;
- /* We've written the chunk; the caller can skip it. */
- skiplen += lenwrit;
- }
- /* Free chunk buffer. */
- free(chunkbuf);
- /* Record the number of chunks we wrote. */
- cce->ccr->nch = cnum;
- /* Update the inode number, file size, and modification time. */
- cce->ccr->ino = cce->ino_new;
- cce->ccr->size = cce->size_new;
- cce->ccr->mtime = cce->mtime_new;
- /* Ask the multitape layer to inform us about later chunks. */
- writetape_setcallback(cookie, callback_addchunk,
- ((cce->cci->trailerusage > cce->cci->chunksusage * 2) ||
- (notrailer != 0)) ? callback_faketrailer : callback_addtrailer,
- cce);
- /* Success! */
- return (skiplen);
- err1:
- free(chunkbuf);
- err0:
- /* Failure! */
- return (-1);
- }
- /**
- * ccache_entry_end(cache, cce, cookie, path, snaptime):
- * The archive entry is ending; clean up callbacks, insert the cache entry
- * into the cache if it isn't already present, and free memory.
- */
- int
- ccache_entry_end(CCACHE * cache, CCACHE_ENTRY * cce, TAPE_W * cookie,
- const char * path, time_t snaptime)
- {
- size_t slen;
- /* Don't want any more callbacks. */
- writetape_setcallback(cookie, NULL, NULL, NULL);
- /*
- * If the cache entry is stale and ccache_entry_writefile was
- * never called, the cached chunks we have are probably not useful
- * (the file was probably truncated to 0 bytes); so remove them.
- */
- if ((cce->ino_new != cce->ccr->ino) ||
- (cce->size_new != cce->ccr->size) ||
- (cce->mtime_new != cce->ccr->mtime))
- cce->ccr->nch = 0;
- /*
- * If the modification time is equal to or after the snapshot time,
- * adjust the modification time to ensure that we will consider this
- * file to be "modified" the next time we see it.
- */
- if (cce->ccr->mtime >= snaptime)
- cce->ccr->mtime = snaptime - 1;
- /* This cache entry is in use and should not be expired yet. */
- cce->ccr->age = 0;
- /*
- * If the entry is worth keeping, make sure it's in the cache;
- * otherwise, free it if it's not already in the cache.
- */
- if ((cce->ccr->nch != 0) || (cce->ccr->tlen != 0)) {
- if (cce->ccrp == NULL) {
- slen = strlen(path);
- if (patricia_insert(cache->tree,
- (const uint8_t *)path, slen, cce->ccr))
- goto err1;
- }
- } else {
- if (cce->ccrp == NULL) {
- if (cce->ccr->nchalloc)
- free(cce->ccr->chp);
- free(cce->ccr);
- }
- }
- /* Free the cache entry cookie. */
- free(cce->trailer);
- free(cce);
- /* Success! */
- return (0);
- err1:
- if (cce->ccr->flags & CCR_ZTRAILER_MALLOC)
- free(cce->ccr->ztrailer);
- if (cce->ccr->nchalloc)
- free(cce->ccr->chp);
- free(cce->ccr);
- free(cce->trailer);
- free(cce);
- /* Failure! */
- return (-1);
- }
- /**
- * ccache_entry_free(cce, cookie):
- * Free the cache entry and cancel callbacks from the multitape layer.
- */
- void
- ccache_entry_free(CCACHE_ENTRY * cce, TAPE_W * cookie)
- {
- if (cce == NULL)
- return;
- /* Don't want any more callbacks. */
- writetape_setcallback(cookie, NULL, NULL, NULL);
- /* If the record isn't in the tree, free it. */
- if (cce->ccrp == NULL) {
- if (cce->ccr->flags & CCR_ZTRAILER_MALLOC)
- free(cce->ccr->ztrailer);
- if (cce->ccr->nchalloc)
- free(cce->ccr->chp);
- free(cce->ccr);
- }
- /* Free the cache entry cookie. */
- free(cce->trailer);
- free(cce);
- }
|