/************************************************************************* * UrBackup - Client/Server backup system * Copyright (C) 2011-2016 Martin Raiber * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . **************************************************************************/ #include "ChunkPatcher.h" #include "../stringtools.h" #include #include "../urbackupcommon/ExtentIterator.h" #include #include #define VLOG(x) namespace { int64 roundUp(int64 numToRound, int64 multiple) { return ((numToRound + multiple - 1) / multiple) * multiple; } int64 roundDown(int64 numToRound, int64 multiple) { return ((numToRound / multiple) * multiple); } bool buf_is_zero(const char* buf, size_t bsize) { for (size_t i = 0; i < bsize; ++i) { if (buf[i] != 0) { return false; } } return true; } } const int64 sparse_blocksize = 512*1024; ChunkPatcher::ChunkPatcher(void) : cb(NULL), require_unchanged(true), with_sparse(false), unchanged_align(0), unchanged_align_start(-1), unchanged_align_end(-1), unchanged_align_end_next(-1), last_unchanged(false) { } void ChunkPatcher::setCallback(IChunkPatcherCallback *pCb) { cb=pCb; } bool ChunkPatcher::ApplyPatch(IFile *file, IFile *patch, ExtentIterator* extent_iterator) { patch->Seek(0); file->Seek(0); _i64 patchf_pos=0; unchanged_align_start = -1; unchanged_align_end = -1; unchanged_align_end_next = -1; last_unchanged = false; const unsigned int buffer_size=512*1024; std::vector buf; buf.resize(buffer_size); if(patch->Read((char*)&filesize, sizeof(_i64))!=sizeof(_i64)) { Server->Log("Error reading patched file size from \""+patch->getFilename()+"\"", LL_ERROR); return false; } filesize = little_endian(filesize); patchf_pos+=sizeof(_i64); if (with_sparse || extent_iterator !=NULL) { last_sparse_start = -1; curr_only_zeros = true; curr_changed = false; sparse_buf.resize(sparse_blocksize); } IFsFile::SSparseExtent curr_sparse_extent; if (extent_iterator != NULL) { curr_sparse_extent = extent_iterator->nextExtent(); } unsigned int max_read = UINT_MAX; if (unchanged_align != 0) { max_read -= UINT_MAX%unchanged_align; } SPatchHeader next_header; next_header.patch_off=-1; next_header.patch_size = 0; bool has_header=true; _i64 file_pos; _i64 size; for(file_pos=0,size=file->Size(); (file_posLog("Read error while reading next patch from \""+patch->getFilename()+"\"", LL_ERROR); return false; } if (next_header.patch_off != -1 && unchanged_align != 0) { unchanged_align_start = roundDown(next_header.patch_off, unchanged_align); unchanged_align_end_next = roundUp(next_header.patch_off + next_header.patch_size, unchanged_align); } } if(!has_header && (file_pos>=filesize || file_pos>=size) ) { break; } unsigned int tr = max_read; if(next_header.patch_off!=-1) { _i64 hoff=next_header.patch_off-file_pos; if(hoff>=0 && hoff=0); } bool patching_finished=false; if(tr==0 && file_pos+next_header.patch_size>filesize) { next_header.patch_size = static_cast(filesize - file_pos); patching_finished=true; } else if(file_pos>=filesize) { Server->Log("Patch corrupt file_pos>=filesize. file_pos="+convert(file_pos)+" next_header.patch_off="+convert(next_header.patch_off)+" next_header.patch_size="+convert(next_header.patch_size)+" tr="+convert(tr)+" size="+convert(size)+" filesize="+convert(filesize)+" has_header="+convert(has_header), LL_ERROR); assert(file_posLog("Applying patch at "+convert(file_pos)+" length="+convert(next_header.patch_size), LL_DEBUG)); unchanged_align_end = unchanged_align_end_next; while(next_header.patch_size>0) { bool has_read_error = false; _u32 r=patch->Read(buf.data(), (std::min)((unsigned int)buffer_size, next_header.patch_size), &has_read_error); if (has_read_error) { Server->Log("Read error while reading patch data from \""+patch->getFilename()+"\"", LL_ERROR); return false; } patchf_pos+=r; if (with_sparse) { nextChunkPatcherBytes(file_pos, buf.data(), r, true, false); } else { assert(cb->chunk_patcher_pos() < 0 || cb->chunk_patcher_pos() == file_pos); cb->next_chunk_patcher_bytes(buf.data(), r, true); } next_header.patch_size-=r; file_pos += r; } if(require_unchanged) { file->Seek(file_pos); } next_header.patch_off=-1; } else if(file_posnextExtent(); } if (file_pos + tr>filesize) { tr = static_cast(filesize - file_pos); } bool was_sparse = false; if (curr_sparse_extent.offset != -1 && tr>=sparse_blocksize && tr>= unchanged_align && curr_sparse_extent.offset <= file_pos && curr_sparse_extent.offset + curr_sparse_extent.size >= file_pos + tr) { if ( (sparse_blocksize == 0 || file_pos%sparse_blocksize == 0) && (unchanged_align == 0 || file_pos%unchanged_align == 0) ) { if (sparse_blocksize != 0 && tr%sparse_blocksize != 0) { tr = tr - tr%sparse_blocksize; } if (unchanged_align != 0 && tr%unchanged_align != 0) { tr = tr - tr%unchanged_align; } VLOG(Server->Log("Sparse extent at " + convert(file_pos) + " length=" + convert(tr), LL_DEBUG)); if (with_sparse) { nextChunkPatcherBytes(file_pos, NULL, tr, false, true); file_pos += tr; } else { bool is_sparse = true; assert(cb->chunk_patcher_pos() < 0 || cb->chunk_patcher_pos() == file_pos); cb->next_chunk_patcher_bytes(NULL, tr, false, &is_sparse); file_pos += tr; } was_sparse = true; } else { if (unchanged_align!=0 && file_pos%unchanged_align != 0) { tr = (std::min)(tr, static_cast(unchanged_align - file_pos%unchanged_align)); } if(sparse_blocksize != 0 && file_pos%sparse_blocksize != 0) { tr = (std::min)(tr, static_cast(sparse_blocksize - file_pos%sparse_blocksize)); } } } while(!was_sparse && tr>0 && file_pos= unchanged_align_start && file_pos < unchanged_align_end_next ) || (unchanged_align_end != -1 && file_pos < unchanged_align_end ) ) { curr_require_unchaged = true; file->Seek(file_pos); } if (!curr_require_unchaged && unchanged_align_start != -1 && file_pos < unchanged_align_start && file_pos + tr > unchanged_align_start) { tr = static_cast<_u32>(unchanged_align_start - file_pos); } else if (unchanged_align_end!= -1 && file_pos < unchanged_align_end && file_pos + tr > unchanged_align_end) { tr = static_cast<_u32>(unchanged_align_end - file_pos); } } if(curr_require_unchaged) { bool has_read_error = false; _u32 r=file->Read(buf.data(), tr, &has_read_error); if (has_read_error) { Server->Log("Read error while reading unchanged data from \""+file->getFilename()+"\"", LL_ERROR); return false; } VLOG(Server->Log("Unchanged required extent at " + convert(file_pos) + " length=" + convert(r), LL_DEBUG)); if (with_sparse) { nextChunkPatcherBytes(file_pos, buf.data(), r, false, false); } else { assert(cb->chunk_patcher_pos() < 0 || cb->chunk_patcher_pos() == file_pos); cb->next_chunk_patcher_bytes(buf.data(), r, false); } file_pos += r; tr-=r; } else { if(file_pos+tr>size) { tr=static_cast(size-file_pos); } VLOG(Server->Log("Unchanged skipped extent at " + convert(file_pos) + " length=" + convert(tr), LL_DEBUG)); bool is_sparse = false; bool* p_is_sparse = &is_sparse; if (unchanged_align != 0 && tr < unchanged_align && last_sparse_start == -1) { p_is_sparse = NULL; } assert(cb->chunk_patcher_pos() < 0 || cb->chunk_patcher_pos() == file_pos); cb->next_chunk_patcher_bytes(NULL, tr, false, p_is_sparse); last_unchanged = true; if (is_sparse) { if (last_sparse_start == -1) { last_sparse_start = file_pos; } } else if (last_sparse_start != -1) { finishSparse(file_pos); } file_pos += tr; tr=0; } if (unchanged_align != 0) { if (file_pos == unchanged_align_end) { unchanged_align_end = -1; } } } } else { Server->Log("Patch corrupt. file_pos="+convert(file_pos)+" next_header.patch_off="+convert(next_header.patch_off)+" next_header.patch_size="+convert(next_header.patch_size)+" tr="+convert(tr)+" size="+convert(size)+" filesize="+convert(filesize), LL_ERROR); assert(false); return false; } if(patching_finished) { if (with_sparse) { finishChunkPatcher(file_pos); } return true; } } if (with_sparse) { finishChunkPatcher(file_pos); } return true; } bool ChunkPatcher::readNextValidPatch(IFile *patchf, _i64 &patchf_pos, SPatchHeader *patch_header, bool& has_read_error) { const unsigned int to_read=sizeof(_i64)+sizeof(unsigned int); do { _u32 r=patchf->Read((char*)&patch_header->patch_off, to_read, &has_read_error); patchf_pos+=r; if(r!=to_read) { patch_header->patch_off=-1; patch_header->patch_size=0; return false; } else { patch_header->patch_off = little_endian(patch_header->patch_off); patch_header->patch_size = little_endian(patch_header->patch_size); } if(patch_header->patch_off==-1) { patchf_pos+=patch_header->patch_size; patchf->Seek(patchf_pos); } } while(patch_header->patch_off==-1); return true; } void ChunkPatcher::nextChunkPatcherBytes(int64 pos, const char * buf, size_t bsize, bool changed, bool sparse) { last_unchanged = false; if (sparse) { assert(pos%sparse_blocksize == 0); assert(unchanged_align==0 || pos%unchanged_align == 0); if (last_sparse_start == -1) { last_sparse_start = roundUp(pos, sparse_blocksize); } bool is_sparse = true; assert(cb->chunk_patcher_pos() < 0 || cb->chunk_patcher_pos() == pos); cb->next_chunk_patcher_bytes(NULL, bsize, changed, &is_sparse); return; } while (bsize > 0) { if (pos%sparse_blocksize == 0 && bsize == sparse_blocksize) { curr_only_zeros = buf_is_zero(buf, bsize); if (curr_only_zeros) { if (last_sparse_start == -1) { last_sparse_start = pos; } bool is_sparse = true; assert(cb->chunk_patcher_pos() < 0 || cb->chunk_patcher_pos() == pos); cb->next_chunk_patcher_bytes(NULL, bsize, changed, &is_sparse); return; } else if (last_sparse_start != -1) { finishSparse(pos); } assert(cb->chunk_patcher_pos() < 0 || cb->chunk_patcher_pos() == pos); cb->next_chunk_patcher_bytes(buf, bsize, changed); curr_only_zeros = true; curr_changed = false; return; } int64 next_checkpoint = roundDown(pos, sparse_blocksize) + sparse_blocksize; size_t bsize_to_checkpoint = (std::min)(bsize, static_cast(next_checkpoint - pos)); int64 sparse_buf_used = pos%sparse_blocksize; if (curr_only_zeros) { for (size_t i = 0; i < bsize_to_checkpoint; ++i) { if (buf[i] != 0) { curr_only_zeros = false; break; } } } if (changed) { curr_changed = true; } memcpy(sparse_buf.data() + sparse_buf_used, buf, bsize_to_checkpoint); if (pos + bsize_to_checkpoint == next_checkpoint) { if (curr_only_zeros) { if (last_sparse_start == -1) { last_sparse_start = next_checkpoint-sparse_blocksize; } } else if(last_sparse_start != -1) { finishSparse(pos); } if (!curr_only_zeros) { cb->next_chunk_patcher_bytes(sparse_buf.data(), sparse_blocksize, curr_changed); assert(cb->chunk_patcher_pos() < 0 || cb->chunk_patcher_pos() == pos + bsize_to_checkpoint); } else { bool is_sparse = true; cb->next_chunk_patcher_bytes(NULL, sparse_blocksize, curr_changed, &is_sparse); assert(cb->chunk_patcher_pos() < 0 || cb->chunk_patcher_pos() == pos + bsize_to_checkpoint); } curr_only_zeros = true; curr_changed = false; } pos += bsize_to_checkpoint; buf += bsize_to_checkpoint; bsize -= bsize_to_checkpoint; } } void ChunkPatcher::finishChunkPatcher(int64 pos) { finishSparse(pos); int64 sparse_buf_used = pos%sparse_blocksize; if (sparse_buf_used > 0 && !last_unchanged) { cb->next_chunk_patcher_bytes(sparse_buf.data(), static_cast(sparse_buf_used), curr_changed); assert(cb->chunk_patcher_pos() < 0 || cb->chunk_patcher_pos() == pos); } } void ChunkPatcher::finishSparse(int64 pos) { if (last_sparse_start!=-1 && roundDown(pos, sparse_blocksize) > last_sparse_start) { IFsFile::SSparseExtent ext(last_sparse_start, roundDown(pos, sparse_blocksize) - last_sparse_start); cb->next_sparse_extent_bytes(reinterpret_cast(&ext), sizeof(IFsFile::SSparseExtent)); last_sparse_start = -1; } } _i64 ChunkPatcher::getFilesize(void) { return filesize; } void ChunkPatcher::setRequireUnchanged( bool b ) { require_unchanged=b; } void ChunkPatcher::setUnchangedAlign(int64 a) { unchanged_align = a; } void ChunkPatcher::setWithSparse(bool b) { with_sparse = b; }