/*************************************************************************
* UrBackup - Client/Server backup system
* Copyright (C) 2011-2014 Martin Raiber
*
* 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 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
**************************************************************************/
#ifndef CLIENT_ONLY
#include "server_writer.h"
#include "../Interface/Mutex.h"
#include "../Interface/Condition.h"
#include "../Interface/Server.h"
#include "../fsimageplugin/IVHDFile.h"
#include "../fsimageplugin/IFSImageFactory.h"
#include "../stringtools.h"
#include "../urbackupcommon/os_functions.h"
#include "server_log.h"
#include "server_cleanup.h"
#include "server_get.h"
extern IFSImageFactory *image_fak;
const size_t free_space_lim=1000*1024*1024; //1000MB
const uint64 filebuf_lim=1000*1024*1024; //1000MB
ServerVHDWriter::ServerVHDWriter(IVHDFile *pVHD, unsigned int blocksize, unsigned int nbufs, int pClientid, bool use_tmpfiles)
{
filebuffer=use_tmpfiles;
clientid=pClientid;
vhd=pVHD;
if(filebuffer)
{
bufmgr=new CBufMgr2(nbufs, sizeof(FileBufferVHDItem)+blocksize);
}
else
{
bufmgr=new CBufMgr2(nbufs, blocksize);
}
if(filebuffer)
{
filebuf=new CFileBufMgr(false);
filebuf_writer=new ServerFileBufferWriter(this, blocksize);
filebuf_writer_ticket=Server->getThreadPool()->execute(filebuf_writer);
currfile=filebuf->getBuffer();
currfile_size=0;
}
mutex=Server->createMutex();
vhd_mutex=Server->createMutex();
cond=Server->createCondition();
exit=false;
exit_now=false;
has_error=false;
written=free_space_lim;
}
ServerVHDWriter::~ServerVHDWriter(void)
{
delete bufmgr;
if(filebuffer)
{
delete filebuf_writer;
delete filebuf;
}
Server->destroy(mutex);
Server->destroy(vhd_mutex);
Server->destroy(cond);
}
void ServerVHDWriter::operator()(void)
{
{
while(!exit_now)
{
BufferVHDItem item;
bool has_item=false;
bool do_exit;
{
IScopedLock lock(mutex);
if(tqueue.empty() && exit==false)
{
cond->wait(&lock);
}
do_exit=exit;
if(!tqueue.empty())
{
item=tqueue.front();
tqueue.pop();
has_item=true;
}
}
if(has_item)
{
if(!has_error)
{
if(!filebuffer)
{
writeVHD(item.pos, item.buf, item.bsize);
}
else
{
FileBufferVHDItem *fbi=(FileBufferVHDItem*)(item.buf-sizeof(FileBufferVHDItem));
fbi->pos=item.pos;
fbi->bsize=item.bsize;
writeRetry(currfile, (char*)fbi, sizeof(FileBufferVHDItem)+item.bsize);
currfile_size+=item.bsize+sizeof(FileBufferVHDItem);
if(currfile_size>filebuf_lim)
{
filebuf_writer->writeBuffer(currfile);
currfile=filebuf->getBuffer();
currfile_size=0;
}
}
}
freeBuffer(item.buf);
}
else if(do_exit)
{
break;
}
if(!filebuffer && written>=free_space_lim/2)
{
written=0;
checkFreeSpaceAndCleanup();
}
}
}
if(filebuffer)
{
filebuf_writer->writeBuffer(currfile);
if(!exit_now)
filebuf_writer->doExit();
else
filebuf_writer->doExitNow();
Server->getThreadPool()->waitFor(filebuf_writer_ticket);
}
if(!vhd->finish())
{
checkFreeSpaceAndCleanup();
if(!vhd->finish())
{
ServerLogger::Log(clientid, "FATAL: Writing failed after cleanup", LL_ERROR);
has_error=true;
}
}
image_fak->destroyVHDFile(vhd);
}
void ServerVHDWriter::checkFreeSpaceAndCleanup(void)
{
std::wstring p;
{
IScopedLock lock(vhd_mutex);
p=ExtractFilePath(vhd->getFilenameW());
}
int64 fs=os_free_space(os_file_prefix(p));
if(fs!=-1 && fs <= free_space_lim )
{
Server->Log("Not enough free space. Waiting for cleanup...");
if(!cleanupSpace())
{
Server->Log("Not enough free space.", LL_WARNING);
}
}
}
void ServerVHDWriter::writeVHD(uint64 pos, char *buf, unsigned int bsize)
{
IScopedLock lock(vhd_mutex);
vhd->Seek(pos);
bool b=vhd->Write(buf, bsize)!=0;
written+=bsize;
if(!b)
{
int retry=3;
for(int i=0;iwait(100);
Server->Log("Retrying writing to VHD file...");
vhd->Seek(pos);
if(vhd->Write(buf, bsize)==0)
{
Server->Log("Writing to VHD file failed");
}
else
{
return;
}
}
std::wstring p=ExtractFilePath(vhd->getFilenameW());
int64 fs=os_free_space(os_file_prefix(p));
if(fs!=-1 && fs <= free_space_lim )
{
Server->Log("Not enough free space. Waiting for cleanup...");
if(cleanupSpace())
{
vhd->Seek(pos);
if(vhd->Write(buf, bsize)==0)
{
retry=3;
for(int i=0;iwait(100);
Server->Log("Retrying writing to VHD file...");
vhd->Seek(pos);
if(vhd->Write(buf, bsize)==0)
{
Server->Log("Writing to VHD file failed");
}
else
{
return;
}
}
ServerLogger::Log(clientid, "FATAL: Writing failed after cleanup", LL_ERROR);
BackupServerGet::sendMailToAdmins("Fatal error occured during image backup", ServerLogger::getWarningLevelTextLogdata(clientid));
has_error=true;
}
}
else
{
has_error=true;
Server->Log("FATAL: NOT ENOUGH free space. Cleanup failed.", LL_ERROR);
BackupServerGet::sendMailToAdmins("Fatal error occured during image backup", ServerLogger::getWarningLevelTextLogdata(clientid));
}
}
else
{
has_error=true;
ServerLogger::Log(clientid, "FATAL: Error writing to VHD-File.", LL_ERROR);
BackupServerGet::sendMailToAdmins("Fatal error occured during image backup", ServerLogger::getWarningLevelTextLogdata(clientid));
}
}
}
char *ServerVHDWriter::getBuffer(void)
{
if(filebuffer)
{
char *buf=bufmgr->getBuffer();
if(buf!=NULL)
{
return buf+sizeof(FileBufferVHDItem);
}
else
{
return buf;
}
}
else
{
return bufmgr->getBuffer();
}
}
void ServerVHDWriter::writeBuffer(uint64 pos, char *buf, unsigned int bsize)
{
IScopedLock lock(mutex);
BufferVHDItem item;
item.pos=pos;
item.buf=buf;
item.bsize=bsize;
tqueue.push(item);
cond->notify_all();
}
void ServerVHDWriter::freeBuffer(char *buf)
{
if(filebuffer)
bufmgr->releaseBuffer(buf-sizeof(FileBufferVHDItem));
else
bufmgr->releaseBuffer(buf);
}
void ServerVHDWriter::doExit(void)
{
IScopedLock lock(mutex);
exit=true;
finish=true;
cond->notify_all();
}
void ServerVHDWriter::doExitNow(void)
{
IScopedLock lock(mutex);
exit=true;
exit_now=true;
finish=true;
cond->notify_all();
}
void ServerVHDWriter::doFinish(void)
{
IScopedLock lock(mutex);
finish=true;
cond->notify_all();
}
size_t ServerVHDWriter::getQueueSize(void)
{
IScopedLock lock(mutex);
return tqueue.size();
}
bool ServerVHDWriter::hasError(void)
{
return has_error;
}
void ServerVHDWriter::setHasError(bool b)
{
has_error=b;
}
IMutex * ServerVHDWriter::getVHDMutex(void)
{
return vhd_mutex;
}
IVHDFile* ServerVHDWriter::getVHD(void)
{
return vhd;
}
bool ServerVHDWriter::cleanupSpace(void)
{
ServerLogger::Log(clientid, "Not enough free space. Cleaning up.", LL_INFO);
if(!ServerCleanupThread::cleanupSpace(free_space_lim) )
{
ServerLogger::Log(clientid, "Could not free space for image. NOT ENOUGH FREE SPACE.", LL_ERROR);
return false;
}
return true;
}
void ServerVHDWriter::freeFile(IFile *buf)
{
filebuf->releaseBuffer(buf);
}
void ServerVHDWriter::writeRetry(IFile *f, char *buf, unsigned int bsize)
{
unsigned int off=0;
while( offWrite(buf+off, bsize-off);
off+=r;
if(offLog("Error writing to file \""+f->getFilename()+"\". Retrying", LL_WARNING);
Server->wait(10000);
}
}
}
//-------------FilebufferWriter-----------------
ServerFileBufferWriter::ServerFileBufferWriter(ServerVHDWriter *pParent, unsigned int pBlocksize) : parent(pParent), blocksize(pBlocksize)
{
mutex=Server->createMutex();
cond=Server->createCondition();
exit=false;
exit_now=false;
written=free_space_lim;
}
ServerFileBufferWriter::~ServerFileBufferWriter(void)
{
while(!fb_queue.empty())
{
parent->freeFile(fb_queue.front());
fb_queue.pop();
}
Server->destroy(mutex);
Server->destroy(cond);
}
void ServerFileBufferWriter::operator()(void)
{
char *blockbuf=new char[blocksize+sizeof(FileBufferVHDItem)];
unsigned int blockbuf_size=blocksize+sizeof(FileBufferVHDItem);
while(!exit_now)
{
IFile* tmp;
bool do_exit;
bool has_item=false;
{
IScopedLock lock(mutex);
while(fb_queue.empty() && exit==false)
{
cond->wait(&lock);
}
do_exit=exit;
if(!fb_queue.empty())
{
has_item=true;
tmp=fb_queue.front();
fb_queue.pop();
}
}
if(has_item)
{
tmp->Seek(0);
uint64 tpos=0;
uint64 tsize=tmp->Size();
while(tposhasError())
{
unsigned int tw=blockbuf_size;
bool old_method=false;
if(twRead(blockbuf, tw)!=tw)
{
old_method=true;
}
else
{
FileBufferVHDItem *item=(FileBufferVHDItem*)blockbuf;
if(tw==item->bsize+sizeof(FileBufferVHDItem) )
{
parent->writeVHD(item->pos, blockbuf+sizeof(FileBufferVHDItem), item->bsize);
written+=item->bsize;
tpos+=item->bsize+sizeof(FileBufferVHDItem);
}
else
{
old_method=true;
}
}
if(old_method==true)
{
tmp->Seek(tpos);
FileBufferVHDItem item;
if(tmp->Read((char*)&item, sizeof(FileBufferVHDItem))!=sizeof(FileBufferVHDItem))
{
Server->Log("Error reading FileBufferVHDItem", LL_ERROR);
exit_now=true;
parent->setHasError(true);
break;
}
tpos+=sizeof(FileBufferVHDItem);
unsigned int tw=item.bsize;
if(tpos+tw>tsize)
{
Server->Log("Size field is wrong", LL_ERROR);
exit_now=true;
parent->setHasError(true);
break;
}
if(tw>blockbuf_size)
{
delete []blockbuf;
blockbuf=new char[tw+sizeof(FileBufferVHDItem)];
blockbuf_size=tw+sizeof(FileBufferVHDItem);
}
if(tmp->Read(blockbuf, tw)!=tw)
{
Server->Log("Error reading from tmp.f", LL_ERROR);
exit_now=true;
parent->setHasError(true);
break;
}
parent->writeVHD(item.pos, blockbuf, tw);
written+=tw;
tpos+=item.bsize;
}
if( written>=free_space_lim/2)
{
written=0;
parent->checkFreeSpaceAndCleanup();
}
}
else
{
break;
}
}
parent->freeFile(tmp);
}
else if(do_exit)
{
break;
}
}
delete []blockbuf;
}
void ServerFileBufferWriter::doExit(void)
{
IScopedLock lock(mutex);
exit=true;
cond->notify_all();
}
void ServerFileBufferWriter::doExitNow(void)
{
IScopedLock lock(mutex);
exit_now=true;
exit=true;
cond->notify_all();
}
void ServerFileBufferWriter::writeBuffer(IFile *buf)
{
IScopedLock lock(mutex);
fb_queue.push(buf);
cond->notify_all();
}
#endif //CLIENT_ONLY