/* xdelta 3 - delta compression tools and library * Copyright (C) 2001, 2002, 2003, 2004, 2005, 2006, 2007, * 2008, 2009, 2010 * Joshua P. MacDonald * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* TODO: This code is heavily revised from 3.0z but still needs major * refactoring. */ #include "xdelta3-internal.h" typedef struct _main_blklru main_blklru; typedef struct _main_blklru_list main_blklru_list; struct _main_blklru_list { main_blklru_list *next; main_blklru_list *prev; }; struct _main_blklru { uint8_t *blk; xoff_t blkno; usize_t size; main_blklru_list link; }; #define MAX_LRU_SIZE 32U #define XD3_MINSRCWINSZ (XD3_ALLOCSIZE * MAX_LRU_SIZE) #define XD3_MAXSRCWINSZ (1ULL << (SIZEOF_USIZE_T * 8 - 1)) #define XD3_INVALID_OFFSET XOFF_T_MAX XD3_MAKELIST(main_blklru_list,main_blklru,link); static usize_t lru_size = 0; static main_blklru *lru = NULL; /* array of lru_size elts */ static main_blklru_list lru_list; static int do_src_fifo = 0; /* set to avoid lru */ static int lru_hits = 0; static int lru_misses = 0; static int lru_filled = 0; static void main_lru_reset (void) { lru_size = 0; lru = NULL; do_src_fifo = 0; lru_hits = 0; lru_misses = 0; lru_filled = 0; } static void main_lru_cleanup (void) { if (lru != NULL) { main_buffree (lru[0].blk); } main_free (lru); lru = NULL; lru_hits = 0; lru_misses = 0; lru_filled = 0; } /* This is called at different times for encoding and decoding. The * encoder calls it immediately, the decoder delays until the * application header is received. */ static int main_set_source (xd3_stream *stream, xd3_cmd cmd, main_file *sfile, xd3_source *source) { int ret = 0; usize_t i; xoff_t source_size = 0; usize_t blksize; XD3_ASSERT (lru == NULL); XD3_ASSERT (stream->src == NULL); XD3_ASSERT (option_srcwinsz >= XD3_MINSRCWINSZ); /* TODO: this code needs refactoring into FIFO, LRU, FAKE. Yuck! * This is simplified from 3.0z which had issues with sizing the * source buffer memory allocation and the source blocksize. */ /* LRU-specific */ main_blklru_list_init (& lru_list); if (allow_fake_source) { /* TODO: refactor * TOOLS/recode-specific: Check "allow_fake_source" mode looks * broken now. */ sfile->mode = XO_READ; sfile->realname = sfile->filename; sfile->nread = 0; } else { /* Either a regular file (possibly compressed) or a FIFO * (possibly compressed). */ if ((ret = main_file_open (sfile, sfile->filename, XO_READ))) { return ret; } /* If the file is regular we know it's size. If the file turns * out to be externally compressed, size_known may change. */ sfile->size_known = (main_file_stat (sfile, &source_size) == 0); } /* Note: The API requires a power-of-two blocksize and srcwinsz * (-B). The logic here will use a single block if the entire file * is known to fit into srcwinsz. */ option_srcwinsz = xd3_xoff_roundup (option_srcwinsz); /* Though called "lru", it is not LRU-specific. We always allocate * a maximum number of source block buffers. If the entire file * fits into srcwinsz, this buffer will stay as the only * (lru_size==1) source block. Otherwise, we know that at least * option_srcwinsz bytes are available. Split the source window * into buffers. */ if ((lru = (main_blklru*) main_malloc (MAX_LRU_SIZE * sizeof (main_blklru))) == NULL) { ret = ENOMEM; return ret; } memset (lru, 0, sizeof(lru[0]) * MAX_LRU_SIZE); /* Allocate the entire buffer. */ if ((lru[0].blk = (uint8_t*) main_bufalloc (option_srcwinsz)) == NULL) { ret = ENOMEM; return ret; } /* Main calls main_getblk_func() once before xd3_set_source(). This * is the point at which external decompression may begin. Set the * system for a single block. */ lru_size = 1; lru[0].blkno = XD3_INVALID_OFFSET; blksize = option_srcwinsz; main_blklru_list_push_back (& lru_list, & lru[0]); XD3_ASSERT (blksize != 0); /* Initialize xd3_source. */ source->blksize = blksize; source->name = sfile->filename; source->ioh = sfile; source->curblkno = XD3_INVALID_OFFSET; source->curblk = NULL; source->max_winsize = option_srcwinsz; if ((ret = main_getblk_func (stream, source, 0)) != 0) { XPR(NT "error reading source: %s: %s\n", sfile->filename, xd3_mainerror (ret)); return ret; } source->onblk = lru[0].size; /* xd3 sets onblk */ /* If the file is smaller than a block, size is known. */ if (!sfile->size_known && source->onblk < blksize) { source_size = source->onblk; sfile->size_known = 1; } /* If the size is not known or is greater than the buffer size, we * split the buffer across MAX_LRU_SIZE blocks (already allocated in * "lru"). */ if (!sfile->size_known || source_size > option_srcwinsz) { /* Modify block 0, change blocksize. */ blksize = option_srcwinsz / MAX_LRU_SIZE; source->blksize = blksize; source->onblk = blksize; /* xd3 sets onblk */ /* Note: source->max_winsize is unchanged. */ lru[0].size = blksize; lru_size = MAX_LRU_SIZE; /* Setup rest of blocks. */ for (i = 1; i < lru_size; i += 1) { lru[i].blk = lru[0].blk + (blksize * i); lru[i].blkno = i; lru[i].size = blksize; main_blklru_list_push_back (& lru_list, & lru[i]); } } if (! sfile->size_known) { /* If the size is not know, we must use FIFO discipline. */ do_src_fifo = 1; } /* Call the appropriate set_source method, handle errors, print * verbose message, etc. */ if (sfile->size_known) { ret = xd3_set_source_and_size (stream, source, source_size); } else { ret = xd3_set_source (stream, source); } if (ret) { XPR(NT XD3_LIB_ERRMSG (stream, ret)); return ret; } XD3_ASSERT (stream->src == source); XD3_ASSERT (source->blksize == blksize); if (option_verbose) { static shortbuf srcszbuf; static shortbuf srccntbuf; static shortbuf winszbuf; static shortbuf blkszbuf; static shortbuf nbufs; if (sfile->size_known) { short_sprintf (srcszbuf, "source size %s [%"Q"]", main_format_bcnt (source_size, &srccntbuf), source_size); } else { short_sprintf (srcszbuf, "%s", "source size unknown"); } nbufs.buf[0] = 0; if (option_verbose > 1) { short_sprintf (nbufs, " #bufs %"Z, lru_size); } XPR(NT "source %s %s blksize %s window %s%s%s\n", sfile->filename, srcszbuf.buf, main_format_bcnt (blksize, &blkszbuf), main_format_bcnt (option_srcwinsz, &winszbuf), nbufs.buf, do_src_fifo ? " (FIFO)" : ""); } return 0; } static int main_getblk_lru (xd3_source *source, xoff_t blkno, main_blklru** blrup, int *is_new) { main_blklru *blru = NULL; usize_t i; (*is_new) = 0; if (do_src_fifo) { /* Direct lookup assumes sequential scan w/o skipping blocks. */ int idx = blkno % lru_size; blru = & lru[idx]; if (blru->blkno == blkno) { (*blrup) = blru; return 0; } /* No going backwards in a sequential scan. */ if (blru->blkno != XD3_INVALID_OFFSET && blru->blkno > blkno) { return XD3_TOOFARBACK; } } else { /* Sequential search through LRU. */ for (i = 0; i < lru_size; i += 1) { blru = & lru[i]; if (blru->blkno == blkno) { main_blklru_list_remove (blru); main_blklru_list_push_back (& lru_list, blru); (*blrup) = blru; return 0; } } } if (do_src_fifo) { int idx = blkno % lru_size; blru = & lru[idx]; } else { XD3_ASSERT (! main_blklru_list_empty (& lru_list)); blru = main_blklru_list_pop_front (& lru_list); main_blklru_list_push_back (& lru_list, blru); } lru_filled += 1; (*is_new) = 1; (*blrup) = blru; blru->blkno = XD3_INVALID_OFFSET; return 0; } static int main_read_seek_source (xd3_stream *stream, xd3_source *source, xoff_t blkno) { xoff_t pos = blkno * source->blksize; main_file *sfile = (main_file*) source->ioh; main_blklru *blru; int is_new; size_t nread = 0; int ret = 0; if (!sfile->seek_failed) { ret = main_file_seek (sfile, pos); if (ret == 0) { sfile->source_position = pos; } } if (sfile->seek_failed || ret != 0) { /* For an unseekable file (or other seek error, does it * matter?) */ if (sfile->source_position > pos) { /* Could assert !IS_ENCODE(), this shouldn't happen * because of do_src_fifo during encode. */ if (!option_quiet) { XPR(NT "source can't seek backwards; requested block offset " "%"Q" source position is %"Q"\n", pos, sfile->source_position); } sfile->seek_failed = 1; stream->msg = "non-seekable source: " "copy is too far back (try raising -B)"; return XD3_TOOFARBACK; } /* There's a chance here, that an genuine lseek error will cause * xdelta3 to shift into non-seekable mode, entering a degraded * condition. */ if (!sfile->seek_failed && option_verbose) { XPR(NT "source can't seek, will use FIFO for %s\n", sfile->filename); if (option_verbose > 1) { XPR(NT "seek error at offset %"Q": %s\n", pos, xd3_mainerror (ret)); } } sfile->seek_failed = 1; if (option_verbose > 1 && pos != sfile->source_position) { XPR(NT "non-seekable source skipping %"Q" bytes @ %"Q"\n", pos - sfile->source_position, sfile->source_position); } while (sfile->source_position < pos) { xoff_t skip_blkno; usize_t skip_offset; xd3_blksize_div (sfile->source_position, source, &skip_blkno, &skip_offset); /* Read past unused data */ XD3_ASSERT (pos - sfile->source_position >= source->blksize); XD3_ASSERT (skip_offset == 0); if ((ret = main_getblk_lru (source, skip_blkno, & blru, & is_new))) { return ret; } XD3_ASSERT (is_new); blru->blkno = skip_blkno; if ((ret = main_read_primary_input (sfile, (uint8_t*) blru->blk, source->blksize, & nread))) { return ret; } if (nread != source->blksize) { IF_DEBUG1 (DP(RINT "[getblk] short skip block nread = %zu\n", nread)); stream->msg = "non-seekable input is short"; return XD3_INVALID_INPUT; } sfile->source_position += nread; blru->size = nread; IF_DEBUG1 (DP(RINT "[getblk] skip blkno %"Q" size %u\n", skip_blkno, blru->size)); XD3_ASSERT (sfile->source_position <= pos); } } return 0; } /* This is the callback for reading a block of source. This function * is blocking and it implements a small LRU. * * Note that it is possible for main_input() to handle getblk requests * in a non-blocking manner. If the callback is NULL then the caller * of xd3_*_input() must handle the XD3_GETSRCBLK return value and * fill the source in the same way. See xd3_getblk for details. To * see an example of non-blocking getblk, see xdelta-test.h. */ static int main_getblk_func (xd3_stream *stream, xd3_source *source, xoff_t blkno) { int ret = 0; xoff_t pos = blkno * source->blksize; main_file *sfile = (main_file*) source->ioh; main_blklru *blru; int is_new; int did_seek = 0; size_t nread = 0; if (allow_fake_source) { source->curblkno = blkno; source->onblk = 0; source->curblk = lru[0].blk; lru[0].size = 0; return 0; } if ((ret = main_getblk_lru (source, blkno, & blru, & is_new))) { return ret; } if (!is_new) { source->curblkno = blkno; source->onblk = blru->size; source->curblk = blru->blk; lru_hits++; return 0; } lru_misses += 1; if (pos != sfile->source_position) { /* Only try to seek when the position is wrong. This means the * decoder will fail when the source buffer is too small, but * only when the input is non-seekable. */ if ((ret = main_read_seek_source (stream, source, blkno))) { return ret; } /* Indicates that another call to main_getblk_lru() may be * needed */ did_seek = 1; } XD3_ASSERT (sfile->source_position == pos); if (did_seek && (ret = main_getblk_lru (source, blkno, & blru, & is_new))) { return ret; } if ((ret = main_read_primary_input (sfile, (uint8_t*) blru->blk, source->blksize, & nread))) { return ret; } /* Save the last block read, used to handle non-seekable files. */ sfile->source_position = pos + nread; if (option_verbose > 3) { if (blru->blkno != XD3_INVALID_OFFSET) { if (blru->blkno != blkno) { XPR(NT "source block %"Q" read %zu ejects %"Q" (lru_hits=%u, " "lru_misses=%u, lru_filled=%u)\n", blkno, nread, blru->blkno, lru_hits, lru_misses, lru_filled); } else { XPR(NT "source block %"Q" read %zu (lru_hits=%u, " "lru_misses=%u, lru_filled=%u)\n", blkno, nread, lru_hits, lru_misses, lru_filled); } } else { XPR(NT "source block %"Q" read %zu (lru_hits=%u, lru_misses=%u, " "lru_filled=%u)\n", blkno, nread, lru_hits, lru_misses, lru_filled); } } source->curblk = blru->blk; source->curblkno = blkno; source->onblk = nread; blru->size = nread; blru->blkno = blkno; IF_DEBUG1 (DP(RINT "[main_getblk] blkno %"Q" onblk %zu pos %"Q" " "srcpos %"Q"\n", blkno, nread, pos, sfile->source_position)); return 0; }