/*************************************************************************
* 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 "vhdfile.h"
#include "../Interface/Server.h"
#include "../Interface/Types.h"
#include "../stringtools.h"
#include "CompressedFile.h"
#include
#include
#include
#include "FileWrapper.h"
#include "ClientBitmap.h"
#ifdef _WIN32
#include
#else
#include
#endif
#include "fs/ntfs.h"
const uint64 def_header_offset=0;
const uint64 def_dynamic_header_offset=512;
const uint64 def_bat_offset=512+1024;
const uint64 def_bat_offset_parent=2*1024;
const int64 unixtime_offset=946684800;
const unsigned int sector_size=512;
VHDFile::VHDFile(const std::string &fn, bool pRead_only, uint64 pDstsize, unsigned int pBlocksize, bool fast_mode, bool compress)
: dstsize(pDstsize), blocksize(pBlocksize), fast_mode(fast_mode), bitmap_offset(0), bitmap_dirty(false), volume_offset(0), finished(false),
file(NULL)
{
compressed_file=NULL;
parent=NULL;
read_only=pRead_only;
is_open=false;
curr_offset=0;
currblock=0xFFFFFFFF;
backing_file = Server->openFile(fn, (read_only ? MODE_READ : MODE_RW));
bool openedExisting = true;
if(!backing_file)
{
if(read_only==false)
{
backing_file = Server->openFile(fn, MODE_RW_CREATE);
openedExisting=false;
}
if(backing_file==NULL)
{
Server->Log("Error opening VHD file", LL_ERROR);
return;
}
}
if(check_if_compressed() || compress)
{
compressed_file = new CompressedFile(backing_file, openedExisting, read_only);
file = compressed_file;
if(compressed_file->hasError())
{
return;
}
}
else
{
file = backing_file;
}
if(file->Size()==0 && !read_only) // created file
{
header_offset=def_header_offset;
dynamic_header_offset=def_dynamic_header_offset;
bat_offset=def_bat_offset;
batsize=(unsigned int)(dstsize/blocksize);
if(dstsize%blocksize!=0)
++batsize;
bat=new unsigned int[batsize];
for(size_t i=0;iSize()-sector_size;
if(nextblock_offset%sector_size!=0)
nextblock_offset+=sector_size-nextblock_offset%sector_size;
is_open=true;
}
}
}
}
}
}
VHDFile::VHDFile(const std::string &fn, const std::string &parent_fn, bool pRead_only, bool fast_mode, bool compress, uint64 pDstsize)
: fast_mode(fast_mode), bitmap_offset(0), bitmap_dirty(false), volume_offset(0), finished(false), file(NULL)
{
compressed_file=NULL;
curr_offset=0;
is_open=false;
read_only=pRead_only;
parent=NULL;
curr_offset=0;
currblock=0xFFFFFFFF;
backing_file=Server->openFile(fn, (read_only?MODE_READ:MODE_RW) );
bool openedExisting = true;
if(!backing_file)
{
if(read_only==false)
{
backing_file=Server->openFile(fn, MODE_RW_CREATE);
openedExisting=false;
}
if(backing_file==NULL)
{
Server->Log("Error opening VHD file", LL_ERROR);
return;
}
}
if(check_if_compressed() || compress)
{
file = new CompressedFile(backing_file, openedExisting, read_only);
}
else
{
file = backing_file;
}
parent=new VHDFile(parent_fn, true, 0);
if(parent->isOpen()==false)
{
Server->Log("Error opening parent VHD", LL_ERROR);
return;
}
dstsize=parent->getRealSize();
blocksize=parent->getBlocksize();
if (pDstsize>0 && pDstsize != dstsize)
{
dstsize = pDstsize;
}
if(file->Size()==0 && !read_only) // created file
{
header_offset=def_header_offset;
dynamic_header_offset=def_dynamic_header_offset;
bat_offset=def_bat_offset_parent;
batsize=(unsigned int)(dstsize/blocksize);
if(dstsize%blocksize!=0)
++batsize;
bat=new unsigned int[batsize];
for(size_t i=0;igetUID(), parent->getTimestamp(), parent_fn );
write_bat();
write_footer();
is_open=true;
}
else
{
if(read_footer() )
{
if(process_footer() )
{
if(read_dynamicheader() )
{
if(read_bat() )
{
nextblock_offset=file->Size()-sector_size;
if(nextblock_offset%sector_size!=0)
nextblock_offset+=sector_size-nextblock_offset%sector_size;
is_open=true;
}
}
}
}
}
}
VHDFile::~VHDFile()
{
if(!finished && file!=NULL)
{
finish();
}
delete file;
delete parent;
}
bool VHDFile::write_header(bool diff)
{
footer.cookie[0]='c';
footer.cookie[1]='o';
footer.cookie[2]='n';
footer.cookie[3]='e';
footer.cookie[4]='c';
footer.cookie[5]='t';
footer.cookie[6]='i';
footer.cookie[7]='x';
footer.features=big_endian((unsigned int)0x00000002);
footer.format_version=big_endian((unsigned int)0x00010000);
footer.data_offset=big_endian(dynamic_header_offset);
footer.timestamp=big_endian((unsigned int)(Server->getTimeSeconds()-unixtime_offset));
footer.creator_application[0]='v';
footer.creator_application[1]='p';
footer.creator_application[2]='c';
footer.creator_application[3]=' ';
footer.creator_version=big_endian((unsigned int)0x00050003);
footer.creator_os=big_endian((unsigned int)0x5769326B);
footer.original_size=big_endian(dstsize);
footer.current_size=big_endian(dstsize);
footer.disk_geometry=calculate_chs();
if(diff)
footer.disk_type=big_endian((unsigned int)4);
else
footer.disk_type=big_endian((unsigned int)3);
footer.checksum=0;
Server->randomFill(footer.uid, 16);
footer.saved_state=0;
memset(footer.reserved, 0, 427);
footer.checksum=calculate_checksum((unsigned char*)&footer, sizeof(VHDFooter) );
if(!file->Seek(header_offset))
return false;
_u32 rc=file->Write((char*)&footer, sizeof(VHDFooter));
if(sizeof(VHDFooter)!=rc)
return false;
else
return true;
}
unsigned int VHDFile::calculate_chs(void)
{
int64 totalSectors=dstsize/sector_size;
unsigned int cylinderTimesHeads;
unsigned char heads;
unsigned char sectorsPerTrack;
if (totalSectors > 65535 * 16 * 255)
{
totalSectors = 65535 * 16 * 255;
}
if (totalSectors >= 65535 * 16 * 63)
{
sectorsPerTrack = 255;
heads = 16;
cylinderTimesHeads = (unsigned int)(totalSectors / sectorsPerTrack);
}
else
{
sectorsPerTrack = 17;
cylinderTimesHeads = (unsigned int)(totalSectors / sectorsPerTrack);
heads = (cylinderTimesHeads + 1023) / 1024;
if (heads < 4)
{
heads = 4;
}
if (cylinderTimesHeads >= ((unsigned int)heads * 1024) || heads > 16)
{
sectorsPerTrack = 31;
heads = 16;
cylinderTimesHeads = (unsigned int)(totalSectors / sectorsPerTrack);
}
if (cylinderTimesHeads >= ((unsigned int)heads * 1024))
{
sectorsPerTrack = 63;
heads = 16;
cylinderTimesHeads = (unsigned int)(totalSectors / sectorsPerTrack);
}
}
unsigned short cylinders = cylinderTimesHeads / heads;
unsigned int chs=(cylinders<<16)+(heads<<8)+sectorsPerTrack;
return big_endian(chs);
}
unsigned int VHDFile::calculate_checksum(const unsigned char * data, size_t dsize)
{
unsigned int checksum=0;
for(size_t i=0;iConvertToUTF16(unicodename_source));
std::string rel_unicodename;
if (dirname.find("Image") != std::string::npos)
{
rel_unicodename = Server->ConvertToUTF16("..\\" +dirname+"\\"+ ExtractFileName(parentfn));
}
else
{
rel_unicodename = Server->ConvertToUTF16(".\\" + ExtractFileName(parentfn));
}
std::string abs_unicodename=Server->ConvertToUTF16(parentfn);
unicodename.resize(unicodename.size()+2);
unicodename[unicodename.size()-2]=0;
unicodename[unicodename.size()-1]=0;
memcpy(dynamicheader.parent_unicodename, &unicodename[0], unicodename.size());
unsigned int locator_blocks_abs=static_cast((abs_unicodename.size())/sector_size)+(((abs_unicodename.size())%sector_size!=0)?1:0);
dynamicheader.parentlocator[0].platform_code=big_endian((unsigned int)0x57326B75);
dynamicheader.parentlocator[0].platform_space=big_endian(locator_blocks_abs*sector_size);
dynamicheader.parentlocator[0].platform_length=big_endian((unsigned int)abs_unicodename.size());
uint64 abs_locator_offset=def_bat_offset_parent-512;
if(locator_blocks_abs>1)
{
abs_locator_offset=nextblock_offset;
nextblock_offset+=abs_locator_offset*sector_size;
}
dynamicheader.parentlocator[0].platform_offset=big_endian(abs_locator_offset);
if(!file->Seek(abs_locator_offset))
return false;
_u32 rc=file->Write(abs_unicodename.c_str(), (_u32)abs_unicodename.size());
if(rc!=abs_unicodename.size())
return false;
dynamicheader.parentlocator[1].platform_code=big_endian((unsigned int)0x57327275);
unsigned int locator_blocks=(rel_unicodename.size())/sector_size+((rel_unicodename.size())%sector_size!=0)?1:0;
if(locator_blocks<128) locator_blocks=128;
dynamicheader.parentlocator[1].platform_space=big_endian(locator_blocks*sector_size);
dynamicheader.parentlocator[1].platform_length=big_endian((unsigned int)rel_unicodename.size());
dynamicheader.parentlocator[1].platform_offset=big_endian(nextblock_offset);
if(!file->Seek(nextblock_offset))
return false;
rc=file->Write(rel_unicodename.c_str(), (_u32)rel_unicodename.size());
if(rc!=rel_unicodename.size())
return false;
nextblock_offset+=locator_blocks*sector_size;
}
init_bitmap();
dynamicheader.checksum=calculate_checksum((unsigned char*)&dynamicheader, sizeof(VHDDynamicHeader) );
if(!file->Seek(dynamic_header_offset))
return false;
_u32 rc=file->Write((char*)&dynamicheader, sizeof(VHDDynamicHeader) );
if(rc!=sizeof(VHDDynamicHeader))
return false;
else
return true;
}
bool VHDFile::write_bat(void)
{
if(!file->Seek(bat_offset)) return false;
_u32 rc=file->Write((char*)bat, batsize*sizeof(unsigned int) );
if(rc!=batsize*sizeof(unsigned int))
return false;
else
return true;
}
bool VHDFile::write_footer(void)
{
if(!file->Seek(nextblock_offset))return false;
_u32 rc=file->Write((char*)&footer, sizeof(VHDFooter));
if (rc != sizeof(VHDFooter))
{
return false;
}
else
{
if (file==backing_file
&& backing_file->Size() != nextblock_offset + sizeof(VHDFooter))
{
backing_file->Resize(nextblock_offset + sizeof(VHDFooter));
}
return true;
}
}
bool VHDFile::read_footer(void)
{
size_t fsize=sizeof(VHDFooter);
bool b=file->Seek(file->Size()-sizeof(VHDFooter));
if(!b)
{
Server->Log("Error seeking -2");
return false;
}
if(file->Read((char*)&footer, sizeof(VHDFooter))!=sizeof(VHDFooter))
{
Server->Log("Cannot read footer", LL_ERROR);
return false;
}
unsigned int checksum=footer.checksum;
footer.checksum=0;
unsigned int cchecksum=calculate_checksum((unsigned char*)&footer, sizeof(VHDFooter) );
if(checksum!=cchecksum)
{
Server->Log("Footer checksum wrong. Switching to header", LL_ERROR);
file->Seek(0);
if(file->Read((char*)&footer, sizeof(VHDFooter))!=sizeof(VHDFooter) )
{
Server->Log("Cannot read footer", LL_ERROR);
return false;
}
else
{
checksum=footer.checksum;
footer.checksum=0;
unsigned int cchecksum=calculate_checksum((unsigned char*)&footer, sizeof(VHDFooter) );
if(checksum!=cchecksum)
{
Server->Log("Header and footer checksum wrong", LL_ERROR);
return false;
}
else
{
footer.checksum=checksum;
return true;
}
}
}
else
{
footer.checksum=checksum;
return true;
}
}
bool VHDFile::process_footer(void)
{
if(big_endian(footer.format_version)!=0x00010000)
{
Server->Log("Unrecognized VHD format version", LL_ERROR);
return false;
}
if(big_endian(footer.disk_type)!=3 && big_endian(footer.disk_type)!=4 )
{
Server->Log("Unsupported disk type", LL_ERROR);
return false;
}
dstsize=big_endian(footer.current_size);
header_offset=0;
dynamic_header_offset=big_endian(footer.data_offset);
return true;
}
bool VHDFile::read_dynamicheader(void)
{
bool b=file->Seek(dynamic_header_offset);
if(!b)
{
Server->Log("Error seeking -2");
return false;
}
_u32 rc=file->Read((char*)&dynamicheader, sizeof(VHDDynamicHeader) );
if(rc!=sizeof(VHDDynamicHeader))
{
Server->Log("Error reading dynamic header", LL_ERROR);
return false;
}
unsigned int checksum=dynamicheader.checksum;
dynamicheader.checksum=0;
unsigned int cchecksum=calculate_checksum((unsigned char*)&dynamicheader, sizeof(VHDDynamicHeader) );
if(checksum!=cchecksum)
{
Server->Log("Dynamicheader checksum wrong", LL_ERROR);
return false;
}
dynamicheader.checksum=checksum;
bat_offset=big_endian(dynamicheader.tableoffset);
batsize=big_endian(dynamicheader.table_entries);
blocksize=big_endian(dynamicheader.blocksize);
if(big_endian(footer.disk_type)==4)
{
//differencing hd
std::string parent_unicodename;
parent_unicodename.resize(512);
memcpy(&parent_unicodename[0], dynamicheader.parent_unicodename, 512);
parent_unicodename=big_endian_utf16(parent_unicodename);
std::wstring parent_fn=Server->ConvertToWchar(Server->ConvertFromUTF16(parent_unicodename));
parent_fn.resize(wcslen(parent_fn.c_str()));
std::string curr_dir = ExtractFilePath(file->getFilename());
bool is_in_other_folder = false;
while(parent_fn.size()>3
&& (parent_fn.find(L"../")==0
|| parent_fn.find(L"..\\")==0 ) )
{
curr_dir = ExtractFilePath(curr_dir);
parent_fn = parent_fn.substr(3);
is_in_other_folder = true;
}
parent_fn=Server->ConvertToWchar(curr_dir)+L"/"+parent_fn;
std::string utf8_parent_fn = Server->ConvertFromWchar(parent_fn);
Server->Log("VHD-Parent: \""+utf8_parent_fn+"\"", LL_INFO);
if (!FileExists(utf8_parent_fn))
{
if (is_in_other_folder)
{
parent_fn = Server->ConvertToWchar(ExtractFilePath(file->getFilename())) + L"/" + Server->ConvertToWchar(ExtractFileName(Server->ConvertFromWchar(parent_fn)));
utf8_parent_fn = Server->ConvertFromWchar(parent_fn);
Server->Log("Corrected VHD-Parent to: \"" + utf8_parent_fn + "\"", LL_INFO);
}
else
{
parent_fn = Server->ConvertToWchar(ExtractFilePath(ExtractFilePath(file->getFilename()))) + L"/" + Server->ConvertToWchar(ExtractFileName(Server->ConvertFromWchar(parent_fn)));
utf8_parent_fn = Server->ConvertFromWchar(parent_fn);
Server->Log("Corrected VHD-Parent to: \"" + utf8_parent_fn + "\"", LL_INFO);
}
}
parent=new VHDFile(utf8_parent_fn, true, 0);
if(parent->isOpen()==false)
{
Server->Log("Error opening Parentvhdfile \""+utf8_parent_fn+"\"", LL_ERROR);
return false;
}
if(memcmp(parent->getUID(), dynamicheader.parent_uid, 16)!=0)
{
Server->Log("Parent uid wrong", LL_ERROR);
return false;
}
if(parent->getTimestamp()!=big_endian(dynamicheader.parent_timestamp) )
{
Server->Log("Parent timestamp wrong. Parent was modified? Continuing anyways. But this is dangerous!", LL_ERROR);
}
}
init_bitmap();
return true;
}
void VHDFile::init_bitmap(void)
{
bitmap_size=blocksize/sector_size/8;
if(blocksize%sector_size!=0 || (blocksize/sector_size)%8!=0)
++bitmap_size;
if(bitmap_size%sector_size!=0)
bitmap_size+=sector_size-bitmap_size%sector_size;
bitmap.resize(bitmap_size);
}
bool VHDFile::read_bat(void)
{
bool b=file->Seek(bat_offset);
if(!b)
{
Server->Log("Error seeking -3");
return false;
}
bat=new unsigned int[batsize];
_u32 rc=file->Read((char*)bat, batsize*sizeof(unsigned int));
if(rc!=batsize*sizeof(unsigned int))
{
Server->Log("Error reading BAT", LL_ERROR);
return false;
}
return true;
}
bool VHDFile::Seek(_i64 offset)
{
curr_offset=(uint64)offset+volume_offset;
return true;
}
inline bool VHDFile::isBitmapSet(unsigned int offset)
{
size_t sector=offset/sector_size;
size_t bitmap_byte=(size_t)(sector/8);
size_t bitmap_bit=sector%8;
unsigned char b=bitmap[bitmap_byte];
bool has_bit=((b & (1<<(7-bitmap_bit)))>0);
return has_bit;
}
inline bool VHDFile::setBitmapBit(unsigned int offset, bool v)
{
size_t sector=offset/sector_size;
size_t bitmap_byte=(size_t)(sector/8);
size_t bitmap_bit=sector%8;
unsigned char b=bitmap[bitmap_byte];
bool has_bit = ((b & (1 << (7 - bitmap_bit)))>0);
if(v==true)
b=b|(1<<(7-bitmap_bit));
else
b=b&(~(1<<(7-bitmap_bit)));
bitmap[bitmap_byte]=b;
if (has_bit != v)
{
bitmap_dirty = true;
return true;
}
else
{
return false;
}
}
bool VHDFile::Read(char* buffer, size_t bsize, size_t &read)
{
unsigned int block=(unsigned int)(curr_offset/blocksize);
size_t blockoffset=curr_offset%blocksize;
size_t remaining=blocksize-blockoffset;
size_t toread=bsize;
bool firstr=true;
read=0;
if(curr_offset>=dstsize)
{
return false;
}
while(true)
{
unsigned int bat_off=big_endian(bat[block]);
if(bat_off==0xFFFFFFFF)
{
unsigned int wantread=(unsigned int)(std::min)(remaining, toread);
if(parent==NULL)
memset(&buffer[read], 0, wantread );
else
{
parent->Seek(curr_offset);
size_t p_read;
bool b=parent->Read(&buffer[read], wantread, p_read);
if(!b)
{
Server->Log("Reading from parent failed -1", LL_ERROR);
}
}
read+=wantread;
curr_offset+=wantread;
blockoffset+=wantread;
remaining-=wantread;
toread-=wantread;
if(toread==0)
break;
else
{
++block;
blockoffset=0;
remaining=blocksize;
continue;
}
}
uint64 dataoffset=(uint64)bat_off*(uint64)sector_size;
if(block!=currblock)
{
switchBitmap(dataoffset);
file->Seek(dataoffset);
if(dataoffset+bitmap_size+blockoffset+bsize>(uint64)file->Size() )
{
Server->Log("Wrong dataoffset: "+convert(dataoffset), LL_ERROR);
return false;
}
if(file->Read(reinterpret_cast(bitmap.data()), bitmap_size)!=bitmap_size)
{
Server->Log("Error reading bitmap", LL_ERROR);
return false;
}
currblock=block;
}
bool b=file->Seek(dataoffset+bitmap_size+blockoffset);
if(!b)
Server->Log("Seeking failed!- 1", LL_ERROR);
while( blockoffsetsector_size-blockoffset%sector_size )
{
wantread=sector_size-blockoffset%sector_size;
firstr=false;
}
else
{
firstr=false;
}
if( curr_offset+wantread>dstsize )
{
return true;
}
if( isBitmapSet((unsigned int)blockoffset) )
{
_u32 curr_tread = (_u32)wantread;
bool has_read_error = false;
wantread=(size_t)file->Read(&buffer[read], curr_tread, &has_read_error);
if (curr_tread != wantread
&& has_read_error)
{
Server->Log("Error reading from VHD file at position " + convert(dataoffset + bitmap_size + blockoffset) + ".");
print_last_error();
return false;
}
}
else
{
if(parent!=NULL)
{
parent->Seek(curr_offset);
bool b=parent->Read(&buffer[read], wantread, wantread);
if(!b)
{
Server->Log("Reading from parent failed -2", LL_ERROR);
}
}
else
{
memset(&buffer[read], 0, wantread );
}
file->Seek(dataoffset+bitmap_size+blockoffset+wantread);
}
read+=wantread;
curr_offset+=wantread;
blockoffset+=wantread;
remaining-=wantread;
toread-=wantread;
if(toread==0)
{
break;
}
if(remaining==0)
{
break;
}
}
if(toread==0)
break;
++block;
blockoffset=0;
remaining=blocksize;
if(curr_offset>=dstsize)
{
return true;
}
}
return true;
}
_u32 VHDFile::Write(const char *buffer, _u32 bsize, bool *has_error)
{
if(read_only)
{
Server->Log("VHD file is read only", LL_ERROR);
if(has_error) *has_error=true;
return 0;
}
if(bsize+curr_offset>dstsize)
{
Server->Log("VHD file is not large enough. Want to write till "+convert(bsize+curr_offset)+" but size is "+convert(dstsize), LL_ERROR);
if(has_error) *has_error=true;
return 0;
}
bool dwrite_footer=false;
uint64 block=curr_offset/((uint64)blocksize);
size_t blockoffset=curr_offset%blocksize;
size_t remaining=blocksize-blockoffset;
size_t towrite=bsize;
size_t bufferoffset=0;
bool firstw=true;
while(true)
{
uint64 dataoffset;
unsigned int bat_ref=big_endian(bat[block]);
bool new_block=false;
if(bat_ref==0xFFFFFFFF)
{
dataoffset=nextblock_offset;
nextblock_offset+=blocksize+bitmap_size;
nextblock_offset=nextblock_offset+(sector_size-nextblock_offset%sector_size);
dwrite_footer=true;
new_block=true;
int64 bat_offset = dataoffset / (uint64)(sector_size);
if (bat_offset >= UINT_MAX)
{
Server->Log("Too much data in VHD file. BAT table overflow. Next BAT entry would be to offset " + convert(bat_offset), LL_ERROR);
if (has_error) *has_error = true;
return 0;
}
bat[block]=big_endian((unsigned int)(bat_offset));
}
else
{
dataoffset=(uint64)bat_ref*(uint64)sector_size;
}
if(currblock!=block)
{
switchBitmap(dataoffset);
bool b=file->Seek(dataoffset);
if(!b)
Server->Log("Seeking failed", LL_ERROR);
if(!new_block)
file->Read(reinterpret_cast(bitmap.data()), bitmap_size);
else
{
memset(bitmap.data(), 0, bitmap_size );
_u32 rc=file->Write(reinterpret_cast(bitmap.data()), bitmap_size);
if(rc!=bitmap_size)
{
Server->Log("Writing bitmap failed", LL_ERROR);
print_last_error();
if(has_error) *has_error=true;
return 0;
}
}
currblock=block;
}
bool b=file->Seek(dataoffset+bitmap_size+blockoffset);
if(!b)
{
Server->Log("Seeking in file failed", LL_ERROR);
if(has_error) *has_error=true;
return 0;
}
while( blockoffsetsector_size-blockoffset%sector_size )
{
wantwrite=sector_size-blockoffset%sector_size;
firstw=false;
}
else
{
firstw=false;
}
setBitmapBit((unsigned int)blockoffset, true);
_u32 rc=file->Write(&buffer[bufferoffset], (_u32)wantwrite);
if(rc!=wantwrite)
{
Server->Log("Writing to file failed", LL_ERROR);
if(has_error) *has_error=true;
print_last_error();
return 0;
}
bufferoffset+=wantwrite;
blockoffset+=wantwrite;
remaining-=wantwrite;
towrite-=wantwrite;
if(towrite==0)
{
break;
}
if(remaining==0)
{
break;
}
}
if(!fast_mode)
{
file->Seek(dataoffset);
_u32 rc=file->Write(reinterpret_cast(bitmap.data()), bitmap_size);
if(rc!=bitmap_size)
{
Server->Log("Writing bitmap failed", LL_ERROR);
print_last_error();
if(has_error) *has_error=true;
return 0;
}
}
if(towrite==0)
break;
++block;
blockoffset=0;
remaining=blocksize;
}
if(dwrite_footer && !fast_mode)
{
if(!write_footer())
{
Server->Log("Error writing footer", LL_ERROR);
if(has_error) *has_error=true;
return 0;
}
if(!write_bat())
{
Server->Log("Error writing BAT", LL_ERROR);
if(has_error) *has_error=true;
return 0;
}
}
curr_offset+=bsize;
return bsize;
}
_u32 VHDFile::Write(int64 spos, const char *buffer, _u32 bsize, bool* has_error)
{
if(!Seek(spos))
{
if (has_error) *has_error = true;
return 0;
}
return Write(buffer, bsize, has_error);
}
bool VHDFile::has_block(bool use_parent)
{
unsigned int block=(unsigned int)(curr_offset/blocksize);
size_t blockoffset=curr_offset%blocksize;
if(curr_offset>=dstsize)
{
return false;
}
unsigned int bat_off=big_endian(bat[block]);
if(bat_off==0xFFFFFFFF)
{
if(parent==NULL || !use_parent)
{
return false;
}
else
{
parent->Seek(curr_offset);
return parent->has_block();
}
}
uint64 dataoffset=(uint64)bat_off*(uint64)sector_size;
if(block!=currblock)
{
switchBitmap(dataoffset);
file->Seek(dataoffset);
if(dataoffset+bitmap_size+blockoffset>(uint64)file->Size() )
{
Server->Log("Wrong dataoffset: "+convert(dataoffset), LL_ERROR);
return false;
}
if(file->Read(reinterpret_cast(bitmap.data()), bitmap_size)!=bitmap_size)
{
Server->Log("Error reading bitmap", LL_ERROR);
return false;
}
currblock=block;
}
if( isBitmapSet((unsigned int)blockoffset) )
{
return true;
}
else
{
if(parent!=NULL && use_parent)
{
parent->Seek(curr_offset);
return parent->has_block();
}
else
{
return false;
}
}
}
void VHDFile::switchBitmap(uint64 new_offset)
{
if(fast_mode && !read_only && bitmap_dirty && bitmap_offset!=0)
{
file->Seek(bitmap_offset);
_u32 rc=file->Write(reinterpret_cast(bitmap.data()), bitmap_size);
if(rc!=bitmap_size)
{
Server->Log("Writing bitmap failed", LL_ERROR);
print_last_error();
}
bitmap_dirty=false;
}
bitmap_offset=new_offset;
bitmap_dirty=false;
}
uint64 VHDFile::getSize(void)
{
return dstsize-volume_offset;
}
uint64 VHDFile::getRealSize(void)
{
return dstsize;
}
uint64 VHDFile::usedSize(void)
{
uint64 offset_backup=curr_offset;
uint64 used_size=0;
for(uint64 i=0;iSeek(curr_offset);
return parent->has_sector();
}
else
return false;
}
else
{
return true;
}
}
bool VHDFile::this_has_sector(_i64 sector_size)
{
unsigned int block=(unsigned int)(curr_offset/blocksize);
unsigned int bat_ref=big_endian(bat[block]);
if(bat_ref==0xFFFFFFFF)
{
return false;
}
else
{
return true;
}
}
char *VHDFile::getUID(void)
{
return footer.uid;
}
unsigned int VHDFile::getTimestamp(void)
{
return big_endian(footer.timestamp);
}
unsigned int VHDFile::getBlocksize()
{
return blocksize;
}
bool VHDFile::isOpen(void)
{
return is_open;
}
std::string VHDFile::getFilename(void)
{
return file->getFilename();
}
std::string VHDFile::Read(_u32 tr, bool *has_error)
{
std::string ret;
ret.resize(4096);
size_t read;
bool b=Read((char*)ret.c_str(), 4096, read);
if(!b)
{
if(has_error) *has_error=true;
ret.clear();
return ret;
}
ret.resize(read);
return ret;
}
std::string VHDFile::Read(int64 spos, _u32 tr, bool* has_error)
{
if(!Seek(spos))
{
if (has_error) *has_error = true;
return std::string();
}
return Read(tr);
}
_u32 VHDFile::Read(char* buffer, _u32 bsize, bool *has_error)
{
size_t read;
bool b=Read(buffer, bsize, read);
if(!b)
{
if(has_error) *has_error=true;
return 0;
}
else
{
return (_u32)read;
}
}
_u32 VHDFile::Read(int64 spos, char* buffer, _u32 bsize, bool* has_error)
{
if(!Seek(spos))
{
if (has_error) *has_error = true;
return 0;
}
return Read(buffer, bsize);
}
_u32 VHDFile::Write(const std::string &tw, bool *has_error)
{
return Write(tw.c_str(), (_u32)tw.size(), has_error);
}
_u32 VHDFile::Write(int64 spos, const std::string &tw, bool *has_error)
{
return Write(spos, tw.c_str(), (_u32)tw.size(), has_error);
}
_i64 VHDFile::Size(void)
{
return (_i64)getSize();
}
_i64 VHDFile::RealSize(void)
{
return (_i64)usedSize();
}
void VHDFile::addVolumeOffset(_i64 offset)
{
volume_offset=offset;
}
void VHDFile::print_last_error()
{
#ifdef _WIN32
Server->Log("Last error: "+convert((int)GetLastError()), LL_ERROR);
#else
Server->Log("Last error: "+convert(errno), LL_ERROR);
#endif
}
bool VHDFile::check_if_compressed()
{
const char header_magic[] = "URBACKUP COMPRESSED FILE";
std::string magic = backing_file->Read(sizeof(header_magic)-1);
return magic == std::string(header_magic);
}
bool VHDFile::finish()
{
if (finished)
{
return true;
}
if (!is_open)
{
return false;
}
switchBitmap(0);
if(fast_mode && !read_only)
{
if(!write_footer())
{
Server->Log("Error writing footer", LL_ERROR);
return false;
}
if(!write_bat())
{
Server->Log("Error writing BAT", LL_ERROR);
return false;
}
}
if(parent!=NULL)
{
if(!parent->finish())
{
return false;
}
}
CompressedFile* compfile = dynamic_cast(file);
if(compfile!=NULL)
{
if (compfile->finish())
{
finished = true;
return true;
}
}
else
{
if (read_only)
{
finished = true;
return true;
}
if (file->Sync())
{
finished = true;
return true;
}
}
return false;
}
VHDFile* VHDFile::getParent()
{
return parent;
}
bool VHDFile::isCompressed()
{
return compressed_file!=NULL;
}
bool VHDFile::makeFull( _i64 fs_offset, IVHDWriteCallback* write_callback)
{
FileWrapper devfile(this, fs_offset);
std::auto_ptr bitmap_source;
bitmap_source.reset(new ClientBitmap(backing_file->getFilename() + ".cbitmap"));
if (bitmap_source->hasError())
{
Server->Log("Error reading client bitmap. Falling back to reading bitmap from NTFS", LL_WARNING);
bitmap_source.reset(new FSNTFS(&devfile, IFSImageFactory::EReadaheadMode_None, false, NULL));
}
if(bitmap_source->hasError())
{
Server->Log("Error opening NTFS bitmap. Cannot convert incremental to full image.", LL_WARNING);
return false;
}
unsigned int bitmap_blocksize = static_cast(bitmap_source->getBlocksize());
std::vector buffer;
buffer.resize(sector_size);
int64 ntfs_blocks_per_vhd_sector = blocksize / bitmap_blocksize;
for(int64 ntfs_block=0, n_ntfs_blocks = devfile.Size()/ bitmap_blocksize;
ntfs_blockhasBlock(i) )
{
has_vhd_sector = true;
break;
}
}
if(has_vhd_sector)
{
int64 block_pos = fs_offset + ntfs_block*bitmap_blocksize;
int64 max_block_pos = (std::min)(fs_offset + ntfs_block*bitmap_blocksize + blocksize,
fs_offset + n_ntfs_blocks*bitmap_blocksize);
for(int64 i = block_pos;iLog("Error converting incremental to full image. Cannot read from parent VHD file at position "+convert(i), LL_WARNING);
return false;
}
if(!write_callback->writeVHD(i, buffer.data(), sector_size))
{
Server->Log("Error converting incremental to full image. Cannot write to VHD file at position "+convert(i), LL_WARNING);
return false;
}
}
}
}
else
{
int64 block_pos = ntfs_block*bitmap_blocksize;
int64 max_block_pos = (std::min)(ntfs_block*bitmap_blocksize + blocksize,
n_ntfs_blocks*bitmap_blocksize);
write_callback->emptyVHDBlock(block_pos, max_block_pos);
}
}
delete parent;
parent = NULL;
Server->Log("Writing new headers...", LL_INFO);
/**
* For some reason Windows won't open the VHD if
* the BAT is not shifted to follow the dynamic header
*/
bat_offset = def_bat_offset;
if(!write_header(false) ||
!write_dynamicheader(NULL, 0, "") ||
!write_footer() ||
!write_bat() )
{
Server->Log("Error writing new headers", LL_WARNING);
return false;
}
return true;
}
bool VHDFile::setUnused(_i64 unused_start, _i64 unused_end)
{
if (!Seek(unused_start))
{
Server->Log("Error while sseking to "+convert(unused_end)+" in VHD file. Size is "+convert(dstsize)+" -2", LL_ERROR);
return false;
}
if (read_only)
{
Server->Log("VHD file is read only -2", LL_ERROR);
return false;
}
if (static_cast(unused_end)>dstsize)
{
Server->Log("VHD file is not large enough. Want to trim till " + convert(unused_end) + " but size is " + convert(dstsize), LL_ERROR);
return false;
}
bool dwrite_footer = false;
uint64 block = curr_offset / ((uint64)blocksize);
size_t blockoffset = curr_offset%blocksize;
size_t remaining = blocksize - blockoffset;
size_t towrite = unused_end- unused_start;
size_t bufferoffset = 0;
bool firstw = true;
std::vector zero_buf;
while (true)
{
uint64 dataoffset;
unsigned int bat_ref = big_endian(bat[block]);
bool new_block = false;
if (bat_ref == 0xFFFFFFFF)
{
dataoffset = nextblock_offset;
nextblock_offset += blocksize + bitmap_size;
nextblock_offset = nextblock_offset + (sector_size - nextblock_offset%sector_size);
dwrite_footer = true;
new_block = true;
bat[block] = big_endian((unsigned int)(dataoffset / (uint64)(sector_size)));
}
else
{
dataoffset = (uint64)bat_ref*(uint64)sector_size;
}
if (currblock != block)
{
switchBitmap(dataoffset);
bool b = file->Seek(dataoffset);
if (!b)
Server->Log("Seeking failed", LL_ERROR);
if (!new_block)
file->Read(reinterpret_cast(bitmap.data()), bitmap_size);
else
{
memset(bitmap.data(), 0, bitmap_size);
_u32 rc = file->Write(reinterpret_cast(bitmap.data()), bitmap_size);
if (rc != bitmap_size)
{
Server->Log("Writing bitmap failed", LL_ERROR);
print_last_error();
return false;
}
}
currblock = block;
}
while (blockoffset < blocksize)
{
size_t wantwrite = (std::min)((size_t)sector_size, towrite);
if (remaining < wantwrite)
wantwrite = remaining;
if (firstw && blockoffset%sector_size != 0 && wantwrite > sector_size - blockoffset%sector_size)
{
wantwrite = sector_size - blockoffset%sector_size;
firstw = false;
}
else
{
firstw = false;
}
/**
* This is counter-intuitive. We want it to return zeroes on reads but if we
* set the bit to false it will read from the parent. So set it to true
* to read the zeroes from the current VHD file.
* This only works if we have not written to the same location previously, so
* write zeroes in this case.
*/
if (!setBitmapBit((unsigned int)blockoffset, true))
{
if (zero_buf.size() != wantwrite)
{
zero_buf.resize(wantwrite);
}
_u32 rc = file->Write(dataoffset + bitmap_size + blockoffset, zero_buf.data(), (_u32)wantwrite);
if (rc != wantwrite)
{
Server->Log("Writing to file failed (2)", LL_ERROR);
print_last_error();
return false;
}
}
bufferoffset += wantwrite;
blockoffset += wantwrite;
remaining -= wantwrite;
towrite -= wantwrite;
if (towrite == 0)
{
break;
}
if (remaining == 0)
{
break;
}
}
if (!fast_mode)
{
file->Seek(dataoffset);
_u32 rc = file->Write(reinterpret_cast(bitmap.data()), bitmap_size);
if (rc != bitmap_size)
{
Server->Log("Writing bitmap failed", LL_ERROR);
print_last_error();
return false;
}
}
if (towrite == 0)
break;
++block;
blockoffset = 0;
remaining = blocksize;
}
if (dwrite_footer && !fast_mode)
{
if (!write_footer())
{
Server->Log("Error writing footer (2)", LL_ERROR);
return false;
}
if (!write_bat())
{
Server->Log("Error writing BAT (2)", LL_ERROR);
return false;
}
}
return true;
}
bool VHDFile::PunchHole( _i64 spos, _i64 size )
{
return false;
}
bool VHDFile::Sync()
{
return finish();
}
bool VHDFile::setBackingFileSize(_i64 fsize)
{
if (file != backing_file)
{
return false;
}
_i64 start_offset = bat_offset + batsize * sizeof(unsigned int);
start_offset = start_offset + (sector_size - start_offset%sector_size);
_i64 one_blocksize = blocksize + bitmap_size;
one_blocksize = one_blocksize + (sector_size - one_blocksize%sector_size);
_i64 nblocks = fsize / blocksize + (fsize%blocksize != 0 ? 1 : 0);
_i64 actual_fsize = start_offset + nblocks*one_blocksize;
if (actual_fsize > backing_file->Size())
{
return backing_file->Resize(actual_fsize, false);
}
else
{
return false;
}
}