/*************************************************************************
*    UrBackup - Client/Server backup system
*    Copyright (C) 2011-2021 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 <http://www.gnu.org/licenses/>.
**************************************************************************/
#include "ImageBackup.h"
#include "../Interface/Server.h"
#include "mbr_code.h"
#include "server_log.h"
#include "server_update_stats.h"
#include "../stringtools.h"
#include "server_cleanup.h"
#include "../fsimageplugin/IVHDFile.h"
#include "../fsimageplugin/IFSImageFactory.h"
#include "server_writer.h"
#include "zero_hash.h"
#include "server_running.h"
#include "../md5.h"
#include "ClientMain.h"
#include <time.h>
#include <memory.h>
#include <stdlib.h>
#include "../Interface/Types.h"
#include <cstring>
#include "../urbackupcommon/sha2/sha2.h"
#include "../Interface/Pipe.h"
#include "../urbackupcommon/fileclient/tcpstack.h"
#include "../urbackupcommon/mbrdata.h"
#include "server_ping.h"
#include "snapshot_helper.h"
#include "server.h"

const unsigned int status_update_intervall=1000;
const unsigned int eta_update_intervall=60000;
const unsigned int sector_size=512;
const unsigned int sha_size=32;
const size_t minfreespace_image=1000*1024*1024; //1000 MB
const unsigned int image_timeout=10*24*60*60*1000;
const unsigned int image_recv_timeout=30*60*1000;
const unsigned int image_recv_timeout_after_first=2*60*1000;
const unsigned int mbr_size=(1024*1024)/2;
const int max_num_hash_errors = 10;

extern std::string server_identity;
extern IFSImageFactory *image_fak;

namespace
{
	void writeZeroblockdata(void)
	{
		const int64 vhd_blocksize=(1024*1024/2);
		unsigned char *zeroes=new unsigned char[vhd_blocksize];
		memset(zeroes, 0, vhd_blocksize);
		unsigned char dig[sha_size];
		sha256(zeroes, vhd_blocksize, dig);
		IFile *out=Server->openFile("zero.hash", MODE_WRITE);
		out->Write((char*)dig, sha_size);
		Server->destroy(out);
		delete []zeroes;
	}

	enum ESendErr
	{
		ESendErr_Ok,
		ESendErr_Read,
		ESendErr_Send
	};

	ESendErr sendFileToPipe(IFile* file, IPipe* outputpipe, logid_t logid)
	{
		file->Seek(0);
		const unsigned int c_send_buffer_size=8192;
		char send_buffer[c_send_buffer_size];
		for(size_t i=0,hsize=(size_t)file->Size();i<hsize;i+=c_send_buffer_size)
		{
			size_t tsend=(std::min)((size_t)c_send_buffer_size, hsize-i);
			if(file->Read(send_buffer, (_u32)tsend)!=tsend)
			{
				ServerLogger::Log(logid, "Reading from file failed. "+os_last_error_str(), LL_ERROR);
				return ESendErr_Read;
			}
			if(!outputpipe->Write(send_buffer, tsend, 120000, false))
			{
				ServerLogger::Log(logid, "Sending file data failed", LL_DEBUG);
				return ESendErr_Send;
			}
		}
		
		if (!outputpipe->Flush(120000))
		{
			ServerLogger::Log(logid, "Flushing file data failed", LL_DEBUG);
			return ESendErr_Send;
		}

		return ESendErr_Ok;
	}
}

ImageBackup::ImageBackup(ClientMain* client_main, int clientid, std::string clientname,
	std::string clientsubname, LogAction log_action, bool incremental, std::string letter, std::string server_token, std::string details,
	bool set_complete, int64 snapshot_id, std::string snapshot_group_loginfo, int64 backup_starttime, bool scheduled)
	: Backup(client_main, clientid, clientname, clientsubname, log_action, false, incremental, server_token, details, scheduled),
	pingthread_ticket(ILLEGAL_THREADPOOL_TICKET), letter(letter), synthetic_full(false), backupid(0), not_found(false),
	set_complete(set_complete), snapshot_id(snapshot_id), mutex(Server->createMutex()),
	snapshot_group_loginfo(snapshot_group_loginfo), backup_starttime(backup_starttime)
{
}

std::vector<ImageBackup::SImageDependency> ImageBackup::getDependencies(bool reset)
{
	IScopedLock lock(mutex.get());
	std::vector<ImageBackup::SImageDependency> ret = ret_dependencies;
	if (reset)
	{
		ret_dependencies.clear();
	}
	return ret;
}

bool ImageBackup::doBackup()
{
	bool cowraw_format = server_settings->getImageFileFormat()==image_file_format_cowraw;

	if(r_incremental)
	{
		ServerLogger::Log(logid, std::string("Starting ")+(scheduled?"scheduled" : "unscheduled")+ " incremental image backup of volume \""+letter+"\"...", LL_INFO);
	}
	else
	{
		ServerLogger::Log(logid, std::string("Starting ") + (scheduled ? "scheduled" : "unscheduled") + " full image backup of volume \""+letter+"\"...", LL_INFO);

		if(cowraw_format)
		{
			synthetic_full=true;
		}
		else if(client_main->isOnInternetConnection())
		{
			if(server_settings->getSettings()->internet_full_image_style==full_image_style_synthetic)
			{
				synthetic_full=true;
			}
		}
		else
		{
			if(server_settings->getSettings()->local_full_image_style==full_image_style_synthetic)
			{
				synthetic_full=true;
			}
		}

		if(letter=="ESP" || letter=="SYSVOL")
		{
			synthetic_full=false;
		}
	}

	if (!snapshot_group_loginfo.empty())
	{
		ServerLogger::Log(logid, "Image backup is being backed up in a snapshot group together with volumes " + snapshot_group_loginfo, LL_INFO);
	}

	bool image_hashed_transfer;
	if(client_main->isOnInternetConnection())
	{
		image_hashed_transfer= (server_settings->getSettings()->internet_image_transfer_mode=="hashed") && client_main->getProtocolVersions().image_protocol_version>0;
	}
	else
	{
		image_hashed_transfer= (server_settings->getSettings()->local_image_transfer_mode=="hashed") && client_main->getProtocolVersions().image_protocol_version>0;
	}

	int sysvol_id=-1;
	int esp_id=-1;
	ScopedLockImageFromCleanup lock_cleanup_sysvol(0);
	ScopedLockImageFromCleanup lock_cleanup_esp(0);
	if(strlower(letter)=="c:")
	{
		ServerLogger::Log(logid, "Backing up SYSVOL...", LL_DEBUG);
		ImageBackup sysvol_backup(client_main, clientid, clientname, clientsubname, LogAction_NoLogging,
			false, "SYSVOL", server_token, "SYSVOL", false, 0, std::string(), 0, scheduled);
		sysvol_backup.setStopBackupRunning(false);
		sysvol_backup();

		if(sysvol_backup.getResult())
		{
			sysvol_id = sysvol_backup.getBackupId();
			lock_cleanup_sysvol.reset(sysvol_id);
		}
		else if (!sysvol_backup.getNotFound()
			&& client_main->getProtocolVersions().efi_version > 0)
		{
			setErrors(sysvol_backup);
			ServerLogger::Log(logid, "Backing up System Reserved (SYSVOL) partition failed. Image backup failed", LL_ERROR);
			return false;
		}
	
		ServerLogger::Log(logid, "Backing up SYSVOL done.", LL_DEBUG);

		if(client_main->getProtocolVersions().efi_version>0)
		{
			ServerLogger::Log(logid, "Backing up EFI System Partition...", LL_DEBUG);
			ImageBackup esp_backup(client_main, clientid, clientname, clientsubname, LogAction_NoLogging,
				false, "ESP", server_token, "ESP", false, 0, std::string(), 0, scheduled);
			esp_backup.setStopBackupRunning(false);
			esp_backup();

			if(esp_backup.getResult())
			{
				esp_id = esp_backup.getBackupId();
				lock_cleanup_esp.reset(esp_id);
			}
			else if (!esp_backup.getNotFound()
				&& client_main->getProtocolVersions().efi_version > 0)
			{
				setErrors(esp_backup);
				ServerLogger::Log(logid, "Backing up EFI System Partition failed. Image backup failed", LL_ERROR);
				return false;
			}

			ServerLogger::Log(logid, "Backing up EFI System Partition done.", LL_DEBUG);
		}
	}

	pingthread = new ServerPingThread(client_main, clientname, status_id, client_main->getProtocolVersions().eta_version>0, server_token);
	pingthread_ticket=Server->getThreadPool()->execute(pingthread, "backup progress update");

	bool ret = false;
	std::string parent_image;
	if(r_incremental || synthetic_full)
	{
		bool incremental_to_last = synthetic_full || cowraw_format;

		if(!incremental_to_last)
		{
			if(client_main->isOnInternetConnection())
			{
				if(server_settings->getSettings()->internet_incr_image_style==incr_image_style_to_last)
				{
					incremental_to_last=true;
				}
			}
			else
			{
				if(server_settings->getSettings()->local_incr_image_style==incr_image_style_to_last)
				{
					incremental_to_last=true;
				}
			}
		}
		
		if(incremental_to_last)
		{
			ServerLogger::Log(logid, "Basing image backup on last incremental or full image backup", LL_INFO);
		}
		else
		{
			ServerLogger::Log(logid, "Basing image backup on last full image backup", LL_INFO);
		}
		
		SBackup last=getLastImage(letter, incremental_to_last);
		if(last.incremental==-2)
		{
			synthetic_full=false;
			ServerLogger::Log(logid, "Error retrieving last image backup. Doing full image backup instead.", LL_WARNING);
			ret = doImage(letter, "", 0, 0, image_hashed_transfer, server_settings->getImageFileFormat(),
					client_main->getProtocolVersions().client_bitmap_version>0,
				client_main->getProtocolVersions().require_previous_cbitmap>0);
		}
		else
		{
			ret = doImage(letter, last.path, last.incremental+1,
				cowraw_format?0:last.incremental_ref, image_hashed_transfer, server_settings->getImageFileFormat(),
				client_main->getProtocolVersions().client_bitmap_version>0,
				client_main->getProtocolVersions().require_previous_cbitmap>0);
		}
	}
	else
	{
		ret = doImage(letter, "", 0, 0, image_hashed_transfer, server_settings->getImageFileFormat(),
			      client_main->getProtocolVersions().client_bitmap_version>0,
			client_main->getProtocolVersions().require_previous_cbitmap>0);
	}

	if(ret)
	{
		if(sysvol_id!=-1)
		{
			backup_dao->saveImageAssociation(backupid, sysvol_id);
			backup_dao->setImageBackupComplete(sysvol_id);
		}

		if(esp_id!=-1)
		{
			backup_dao->saveImageAssociation(backupid, esp_id);
			backup_dao->setImageBackupComplete(esp_id);
		}
	}

	if(ret
		&& letter!="SYSVOL"
		&& letter!="ESP"
		&& set_complete
		&& dependencies.empty() )
	{
		backup_dao->updateClientLastImageBackup(backupid, clientid);
	}

	if(pingthread!=NULL)
	{
		pingthread->setStop(true);
	}

	return ret;
}

namespace
{
	enum ETransferState
	{
		ETransferState_First,
		ETransferState_Bitmap,
		ETransferState_Image
	};
	
	const unsigned char ImageFlag_Persistent=1;
	const unsigned char ImageFlag_Bitmap=2;
}


bool ImageBackup::doImage(const std::string &pLetter, const std::string &pParentvhd, int incremental, int incremental_ref,
	bool transfer_checksum, std::string image_file_format, bool transfer_bitmap, bool transfer_prev_cbitmap)
{
	std::string sletter = conv_filename(pLetter);
	if (pLetter != "SYSVOL" && pLetter != "ESP")
	{
		if ((pLetter.size() <= 3
			&& pLetter[1] == ':'
			&& (pLetter[2] == '\\'
				|| pLetter[2] == '/'))
			|| (pLetter.size() <= 2
				&& pLetter[1] == ':'))
		{
			sletter = sletter.substr(0,1);
		}
	}

	std::string imagefn;
	bool fatal_mbr_error;
	std::string loadfn;
	bool disk_backup = false;
	std::string mbrd = getMBR(sletter, pLetter, pParentvhd.empty(), snapshot_id, fatal_mbr_error, loadfn);
	if (mbrd.empty())
	{
		if (pLetter != "SYSVOL" && pLetter != "ESP")
		{
			if (fatal_mbr_error)
			{
				ServerLogger::Log(logid, "Cannot retrieve master boot record (MBR) for the disk from the client.", LL_ERROR);
				return false;
			}
			else
			{
				ServerLogger::Log(logid, "Cannot retrieve master boot record (MBR) for the disk from the client. "
					"Continuing backup but you will not be able to restore this image backup via restore CD.", LL_WARNING);
			}
		}
	}
	else
	{
		imagefn = constructImagePath(sletter, image_file_format, pParentvhd);

		if (imagefn.empty())
		{
			ServerLogger::Log(logid, "Error creating image backup destination.", LL_ERROR);
			return false;
		}

		std::unique_ptr<IFsFile> mbr_file(Server->openFile(os_file_prefix(imagefn + ".mbr"), MODE_WRITE));

		if (mbr_file.get() != NULL)
		{
			_u32 w = mbr_file->Write(mbrd);
			if (w != mbrd.size())
			{
				ServerLogger::Log(logid, "Error writing mbr data. " + os_last_error_str(), LL_ERROR);
				return false;
			}
		}
		else
		{
			ServerLogger::Log(logid, "Error creating file for writing MBR data. " + os_last_error_str(), LL_ERROR);
			return false;
		}

		if(!loadfn.empty())
		{
			disk_backup = true;

			FileClient fc(false, client_main->getIdentity(), client_main->getProtocolVersions().filesrv_protocol_version,
				client_main->isOnInternetConnection(), client_main);
			_u32 rc = client_main->getClientFilesrvConnection(&fc, server_settings.get(), 60000);
			if (rc != ERR_CONNECTED)
			{
				ServerLogger::Log(logid, "Error getting MBR zip data - CONNECT error", LL_ERROR);
				return false;
			}

			ServerLogger::Log(logid, clientname + ": Loading MBR zip file...", LL_INFO);

			int64 full_backup_starttime = Server->getTimeMS();

			int64 starttime = Server->getTimeMS();

			IFsFile* tmpf = Server->openTemporaryFile();
			if (tmpf == NULL)
			{
				ServerLogger::Log(logid, "Error opening temporay file for MBR zip load", LL_ERROR);
				return false;
			}

			ScopedDeleteFile tmpf_del(tmpf);

			bool has_err_file = true;
			{
				_u32 rc2 = fc.GetFile(loadfn + ".err", tmpf, true, false, 0, false, 0);
				if (rc2 == ERR_CANNOT_OPEN_FILE)
				{
					has_err_file = false;
				}
			}

			tmpf->Resize(0, false);
			tmpf->Seek(0);
			rc = ERR_CANNOT_OPEN_FILE;
			int retry = 0;
			while (Server->getTimeMS() - starttime < 60 * 60 * 1000
				&& rc== ERR_CANNOT_OPEN_FILE)
			{
				rc = fc.GetFile(loadfn, tmpf, true, false, 0, false, 0);

				if (rc != ERR_TIMEOUT
					&& rc!=ERR_SUCCESS)
				{
					if (retry < 3)
					{
						Server->wait(1000);
					}
					else
					{
						if (!has_err_file)
						{
							tmpf->Resize(0, false);
							tmpf->Seek(0);
							_u32 rc2 = fc.GetFile(loadfn + ".err", tmpf, true, false, 0, false, 0);

							if (rc2 == ERR_SUCCESS)
							{
								if (tmpf->Size() < 100 * 1024)
									ServerLogger::Log(logid, "Error loading ZIP file MBR (1): " + tmpf->Read(0LL, static_cast<_u32>(tmpf->Size())), LL_ERROR);
								else
									ServerLogger::Log(logid, "Error loading ZIP file MBR (1)(error too large)", LL_ERROR);
								return false;
							}
						}

						Server->wait(20000);
					}
					++retry;
				}
			}

			if (rc != ERR_SUCCESS)
			{
				tmpf->Resize(0, false);
				tmpf->Seek(0);
				_u32 rc2 = fc.GetFile(loadfn+".err", tmpf, true, false, 0, false, 0);

				if (rc2 == ERR_SUCCESS)
				{
					if(tmpf->Size()<100 * 1024)
						ServerLogger::Log(logid, "Error loading ZIP file MBR: "+tmpf->Read(0LL, static_cast<_u32>(tmpf->Size()) ), LL_ERROR);
					else
						ServerLogger::Log(logid, "Error loading ZIP file MBR (error too large)", LL_ERROR);
					return false;
				}
				else
				{
					ServerLogger::Log(logid, "Error loading ZIP file MBR: "+fc.getErrorString(rc), LL_ERROR);
					return false;
				}
			}

			char buf[1024];
			tmpf->Seek(0);
			bool has_read_error=false;
			while ((rc = tmpf->Read(buf, sizeof(buf), &has_read_error)) > 0)
			{
				if (mbr_file->Write(buf, rc) != rc)
				{
					ServerLogger::Log(logid, "Error writing to "+mbr_file->getFilename()+". "+os_last_error_str(), LL_ERROR);
					return false;
				}
			}

			if (has_read_error)
			{
				ServerLogger::Log(logid, "Error reading from " + tmpf->getFilename() + ". " + os_last_error_str(), LL_ERROR);
				return false;
			}

			ServerLogger::Log(logid, clientname + ": Loaded MBR zip file (" + PrettyPrintBytes(tmpf->Size()) + ")", LL_INFO);

			if (snapshot_id == 0)
			{
				tmpf->Resize(0, false);
				tmpf->Seek(0);
				_u32 rc2 = fc.GetFile(loadfn + ".save_id", tmpf, true, false, 0, false, 0);
				if (rc2 == ERR_SUCCESS
					&& tmpf->Size() < 100)
				{
					snapshot_id = watoi(tmpf->Read(0LL, static_cast<_u32>(tmpf->Size())));
				}
			}
		}
	}

	ScopedLockImageFromCleanup cleanup_lock(0);

	if (!addBackupToDatabase(pLetter, pParentvhd, incremental,
		incremental_ref, imagefn, cleanup_lock, NULL))
	{
		ServerLogger::Log(logid, "Error creating image backup row in database", LL_ERROR);
		return false;
	}

	CTCPStack tcpstack(client_main->isOnInternetConnection());
	IPipe *cc=client_main->getClientCommandConnection(server_settings.get(), 60000);
	if(cc==NULL)
	{
		ServerLogger::Log(logid, "Connecting to \""+clientname+"\" for image backup failed", LL_ERROR);
		has_timeout_error = true;
		return false;
	}

	bool with_checksum=false;
	std::string chksum_str="";
	if(transfer_checksum && client_main->getProtocolVersions().image_protocol_version>0)
	{
		chksum_str="&checksum=1";
		with_checksum=true;
	}
	
	if(transfer_bitmap)
	{
		chksum_str+="&bitmap=1";
	}

	if (!clientsubname.empty())
	{
		chksum_str += "&clientsubname=" + EscapeParamString(clientsubname);
	}

	chksum_str += "&status_id=" + convert(status_id);

	std::string identity = client_main->getIdentity();

	chksum_str += "&running_jobs=" + convert(ServerStatus::numRunningJobs(clientname));

	if (snapshot_id != 0)
	{
		chksum_str += "&shadowid=" + convert(snapshot_id);
	}

	chksum_str += "&zero_skipped=1";

	if(pParentvhd.empty())
	{
		tcpstack.Send(cc, identity+"FULL IMAGE letter="+pLetter+"&token="+server_token+chksum_str);
	}
	else
	{
		std::unique_ptr<IFile> hashfile(Server->openFile(os_file_prefix(pParentvhd + ".hash")));
		if(hashfile.get()==NULL)
		{
			ServerLogger::Log(logid, "Error opening hashfile ("+ pParentvhd + ".hash). "+os_last_error_str(), LL_ERROR);
			Server->Log("Starting image path repair...", LL_INFO);
			ServerUpdateStats::repairImages();
			Server->destroy(cc);
			return false;
		}
		std::unique_ptr<IFile> prevbitmap;
		std::string prevbitmap_str;
		if (transfer_prev_cbitmap)
		{
			prevbitmap.reset(Server->openFile(os_file_prefix(pParentvhd + ".cbitmap")));
			if (prevbitmap.get() == NULL)
			{
				ServerLogger::Log(logid, "Error opening previous bitmap ("+ pParentvhd + ".cbitmap). "+os_last_error_str()+". Backup will proceed without CBT.", LL_WARNING);
			}
			else
			{
				prevbitmap_str = "&cbitmapsize=" + convert(prevbitmap->Size());
			}
		}
		
		std::string ts=identity+"INCR IMAGE letter="+pLetter+"&hashsize="+convert(hashfile->Size())+"&token="+server_token+chksum_str+ prevbitmap_str;
		size_t rc=tcpstack.Send(cc, ts);
		if(rc==0)
		{
			ServerLogger::Log(logid, "Sending 'INCR IMAGE' command failed", LL_ERROR);
			Server->destroy(cc);
			return false;
		}
		ESendErr senderr = sendFileToPipe(hashfile.get(), cc, logid);
		if(senderr!=ESendErr_Ok)
		{
			if (senderr == ESendErr_Send)
			{
				ServerLogger::Log(logid, "Exchanging data with client during image backup preparation failed. Connection to client broke.", LL_ERROR);
				has_timeout_error = true;
			}
			else
			{
				ServerLogger::Log(logid, "Exchanging data with client during image backup preparation failed. Server cannot read necessary data.", LL_ERROR);
			}
			Server->destroy(cc);
			return false;
		}

		if (prevbitmap.get() != NULL)
		{
			ESendErr senderr = sendFileToPipe(prevbitmap.get(), cc, logid);
			if (senderr != ESendErr_Ok)
			{
				if (senderr == ESendErr_Send)
				{
					ServerLogger::Log(logid, "Exchanging data with client during image backup preparation failed. Connection to client broke.", LL_ERROR);
					has_timeout_error = true;
				}
				else
				{
					ServerLogger::Log(logid, "Exchanging data with client during image backup preparation failed. Server cannot read necessary data.", LL_ERROR);
				}
				Server->destroy(cc);
				return false;
			}
		}
	}

	std::string ret;
	int64 starttime=Server->getTimeMS();
	bool first=true;
	const unsigned int c_buffer_size=32768;
	std::vector<char> buffer;
	buffer.resize(c_buffer_size);
	unsigned int blocksize=0xFFFFFFFF;
	unsigned int blockleft=0;
	int64 currblock=-1;
	char *blockdata=NULL;
	int64 drivesize;
	ServerVHDWriter *vhdfile=NULL;
	THREADPOOL_TICKET vhdfile_ticket;
	IVHDFile *r_vhdfile=NULL;
	IFile *hashfile=NULL;
	IFile *parenthashfile=NULL;
	std::unique_ptr<IFile> bitmap_file;
	int64 blockcnt=0;
	int64 numblocks=0;
	int64 blocks=0;
	int64 totalblocks=0;
	int64 mbr_offset=0;
	_u32 off=0;
	bool persistent=false;
	std::vector<unsigned char> zeroblockdata;
	int64 nextblock=0;
	int64 last_verified_block=0;
	int64 vhd_blocksize=(1024*1024)/2;
	ServerRunningUpdater *running_updater=new ServerRunningUpdater(backupid, true);
	Server->getThreadPool()->execute(running_updater, "backup active update");
	unsigned char verify_checksum[sha_size];
	bool warned_about_parenthashfile_error=false;
	bool internet_connection = client_main->isOnInternetConnection();

	bool has_parent=false;
	if(!pParentvhd.empty())
		has_parent=true;

	sha256_ctx shactx;
	sha256_init(&shactx);

	_i64 transferred_bytes=0;
	_i64 transferred_bytes_real=0;
	int64 image_backup_starttime=Server->getTimeMS();

	unsigned int curr_image_recv_timeout=image_recv_timeout;

	int64 last_status_update=0;
	int64 last_eta_update=0;
	int64 last_eta_update_blocks=0;
	double eta_estimated_speed = 0;
	int64 eta_set_time = Server->getTimeMS();
	int64 speed_set_time = eta_set_time;
	int64 last_speed_received_bytes = 0;
	ServerStatus::setProcessEtaSetTime(clientname, status_id, eta_set_time);
	ETransferState transfer_state = ETransferState_First;
	unsigned char image_flags=0;
	size_t bitmap_read = 0;
	

	int num_hash_errors=0;

	while(Server->getTimeMS()-starttime<=image_timeout)
	{
		if(ServerStatus::getProcess(clientname, status_id).stop)
		{
			ServerLogger::Log(logid, "Server admin stopped backup.", LL_ERROR);
			goto do_image_cleanup;
		}
		size_t r=0;
		if(cc!=NULL)
		{
			r=cc->Read(&buffer[off], c_buffer_size-off, curr_image_recv_timeout);
		}
		if(r!=0)
			r+=off;
		starttime=Server->getTimeMS();
		off=0;
		if(r==0 )
		{
			curr_image_recv_timeout = image_recv_timeout;
			ServerStatus::setProcessSpeed(clientname, status_id, 0);
			ServerStatus::setProcessEta(clientname, status_id, -1);
			if(persistent && nextblock!=0)
			{
				int64 continue_block=nextblock;
				if(continue_block%vhd_blocksize!=0 )
				{
					continue_block=(continue_block/vhd_blocksize)*vhd_blocksize;
				}
				else if (with_checksum 
					&& continue_block != (last_verified_block+ vhd_blocksize)
					&& continue_block != last_verified_block)
				{
					//We haven't received a checksum for this block yet. Start one block back.
					continue_block = ((continue_block / vhd_blocksize)-1)*vhd_blocksize;
				}

				bool reconnected=false;
				while(Server->getTimeMS()-starttime<=image_timeout)
				{
					if(ServerStatus::getProcess(clientname, status_id).stop)
					{
						ServerLogger::Log(logid, "Server admin stopped backup. (2)", LL_ERROR);
						ServerStatus::setROnline(clientname, true);
						goto do_image_cleanup;
					}
					ServerStatus::setROnline(clientname, false);
					if(cc!=NULL)
					{
						transferred_bytes+=cc->getTransferedBytes();
						transferred_bytes_real+=cc->getRealTransferredBytes();
						Server->destroy(cc);
						cc = NULL;
					}

					if (!internet_connection 
						&& client_main->isOnInternetConnection()
						&& !server_settings->getSettings()->internet_image_backups )
					{
						Server->Log(clientname+": Image client is connected via Internet now. Waiting for client to return to local network...", LL_DEBUG);
						Server->wait(60000);
					}
					else if (!client_main->isDataplanOkay(server_settings.get(), false))
					{
						Server->Log(clientname+": Image client is on a limited data plan now. Waiting for client to return to a better dataplan...", LL_DEBUG);
						Server->wait(60000);
					}
					else
					{
						Server->Log(clientname +": Trying to reconnect in doImage", LL_DEBUG);
						cc = client_main->getClientCommandConnection(server_settings.get(), 60000);
						if (cc == NULL)
						{
							Server->wait(60000);
						}
						else
						{
							Server->Log(clientname + ": Connected. Authenticating...", LL_DEBUG);
							if (!client_main->authenticateIfNeeded(false,
								internet_connection!=client_main->isOnInternetConnection()))
							{
								Server->destroy(cc);
								cc = NULL;
								Server->wait(60000);
							}
							else
							{
								identity = client_main->getIdentity();
								reconnected = true;
								ServerStatus::setROnline(clientname, true);
								Server->Log(clientname + ": Reconnected.", LL_DEBUG);
								internet_connection = client_main->isOnInternetConnection();
								break;
							}
						}
					}					
				}

				if(!reconnected)
				{
					ServerLogger::Log(logid, "Timeout while trying to reconnect", LL_ERROR);
					ServerStatus::setROnline(clientname, true);
					goto do_image_cleanup;
				}

				if(pParentvhd.empty())
				{
					std::string cmd = identity+"FULL IMAGE letter="+pLetter+"&start="+convert(continue_block)
						+"&shadowid="+convert(snapshot_id)+ "&status_id=" + convert(status_id)+"&token="+ server_token;
					if(transfer_bitmap)
					{
						cmd+="&bitmap=1";
					}
					if(with_checksum)
					{
						cmd+="&checksum=1";
					}
					if (!clientsubname.empty())
					{
						cmd += "&clientsubname=" + EscapeParamString(clientsubname);
					}
					cmd += "&zero_skipped=1";
					size_t sent = tcpstack.Send(cc, cmd);
					if(sent==0)
					{
						ServerLogger::Log(logid, "Sending 'FULL IMAGE' command failed", LL_WARNING);
						transferred_bytes+=cc->getTransferedBytes();
						transferred_bytes_real+=cc->getRealTransferredBytes();
						Server->destroy(cc);
						cc=NULL;
						continue;
					}
				}
				else
				{
					std::string ts = "INCR IMAGE letter="+pLetter+"&start=" + convert(continue_block) + "&shadowid=" + convert(snapshot_id) + "&hashsize="
						+ convert(parenthashfile->Size()) + "&status_id=" + convert(status_id) + "&token=" + server_token;
					if(transfer_bitmap)
					{
						ts+="&bitmap=1";
					}
					if(with_checksum)
					{
						ts+="&checksum=1";
					}
					if (!clientsubname.empty())
					{
						ts += "&clientsubname=" + EscapeParamString(clientsubname);
					}
					ts += "&zero_skipped=1";
					std::unique_ptr<IFile> prevbitmap;
					if (transfer_prev_cbitmap)
					{
						prevbitmap.reset(Server->openFile(os_file_prefix(pParentvhd + ".cbitmap")));

						if (prevbitmap.get() != NULL)
						{
							ts += "&cbitmapsize=" + convert(prevbitmap->Size());
						}
						else
						{
							ServerLogger::Log(logid, "Opening previous client bitmap ("+ pParentvhd + ".cbitmap) failed during reconnect. This might cause the backup to fail.", LL_WARNING);
						}
					}
					size_t sent=tcpstack.Send(cc, identity+ts);
					if(sent==0)
					{
						ServerLogger::Log(logid, "Sending 'INCR IMAGE' command failed", LL_DEBUG);
						transferred_bytes+=cc->getTransferedBytes();
						transferred_bytes_real+=cc->getRealTransferredBytes();
						Server->destroy(cc);
						cc=NULL;
						continue;
					}
					ESendErr rc = sendFileToPipe(parenthashfile, cc, logid);
					if(rc==ESendErr_Send)
					{
						ServerLogger::Log(logid, "Sending hash data failed", LL_DEBUG);
						transferred_bytes+=cc->getTransferedBytes();
						transferred_bytes_real+=cc->getRealTransferredBytes();
						Server->destroy(cc);
						cc=NULL;
						continue;
					}
					else if(rc!=ESendErr_Ok)
					{
						ServerLogger::Log(logid, "Reading from hashfile failed", LL_ERROR);
						goto do_image_cleanup;
					}

					
					if (prevbitmap.get() != NULL)
					{
						rc = sendFileToPipe(prevbitmap.get(), cc, logid);
						if (rc == ESendErr_Send)
						{
							ServerLogger::Log(logid, "Sending bitmap data failed", LL_DEBUG);
							transferred_bytes += cc->getTransferedBytes();
							transferred_bytes_real += cc->getRealTransferredBytes();
							Server->destroy(cc);
							cc = NULL;
							continue;
						}
						else if (rc != ESendErr_Ok)
						{
							ServerLogger::Log(logid, "Reading from bitmap file failed", LL_ERROR);
							goto do_image_cleanup;
						}
					}
				}
				off=0;
				starttime=Server->getTimeMS();

				blockleft=0;
				currblock=-1;
			}
			else
			{
				has_timeout_error = true;
				if (!persistent)
				{
					ServerLogger::Log(logid, "Client disconnected and image is not persistent (Timeout: " + (cc == NULL ? "unavailable" : convert(!cc->hasError()))+").", LL_ERROR);
				}
				else
				{
					ServerLogger::Log(logid, "Client disconnected before sending anything (Timeout: " + (cc == NULL ? "unavailable" : convert(!cc->hasError())) + ").", LL_ERROR);
				}
				goto do_image_cleanup;
			}
		}
		else
		{
			if(transfer_state==ETransferState_First)
			{
				transfer_state = ETransferState_Image;
				size_t csize=sizeof(unsigned int);
				_u32 off_start=off;
				if(r>=csize)
				{
					memcpy(&blocksize, buffer.data(), sizeof(unsigned int) );
					blocksize = little_endian(blocksize);
					off+=sizeof(unsigned int);
					vhd_blocksize/=blocksize;
				}
				if(blocksize==0xFFFFFFFF)
				{
					off+=sizeof(unsigned int);
					if(r>sizeof(uint64))
					{
						std::string err;
						err.resize(r-sizeof(uint64) );
						memcpy(&err[0], &buffer[off], r-off);

						if (next(err, 0, "Ident reset"))
						{
							client_main->forceReauthenticate();
						}

						if(pLetter!="SYSVOL" && pLetter!="ESP")
						{
							ServerLogger::Log(logid, "Request of image backup failed. Reason: "+err, LL_ERROR);

							if (err == "Need previous client bitmap")
							{
								client_main->updateCapa();
							}
						}
						else
						{
							int loglevel = LL_WARNING;

							if (err == "Not found")
							{
								loglevel = LL_DEBUG;
								not_found = true;
							}

							if(pLetter=="SYSVOL")
								ServerLogger::Log(logid, "Request of SYSVOL failed. Reason: "+err, loglevel);
							else
								ServerLogger::Log(logid, "Request of EFI System Partition failed. Reason: "+err, loglevel);
						}
					}
					else
					{
						ServerLogger::Log(logid, "Error on client. No reason given.", LL_ERROR);
					}
					goto do_image_cleanup;
				}
				bool issmall=false;
				csize+=sizeof(int64);
				if(r>=csize)
				{
					memcpy(&drivesize, &buffer[off], sizeof(int64) );
					drivesize=little_endian(drivesize);
					off+=sizeof(int64);

					blockcnt=drivesize/blocksize;
					blocks=blockcnt;
					totalblocks=blockcnt;

					if(drivesize%blocksize!=0)
						++totalblocks;

					zeroblockdata.resize(blocksize);

					if (imagefn.empty())
					{
						imagefn = constructImagePath(sletter, image_file_format, pParentvhd);

						if (imagefn.empty())
						{
							goto do_image_cleanup;
						}

						if (!addBackupToDatabase(pLetter, pParentvhd, incremental,
							incremental_ref, imagefn, cleanup_lock, running_updater))
						{
							ServerLogger::Log(logid, "Error creating image backup row in database -2", LL_ERROR);
							goto do_image_cleanup;
						}
					}

					IFSImageFactory::ImageFormat image_format;

					if(image_file_format==image_file_format_vhd)
					{
						image_format = IFSImageFactory::ImageFormat_VHD;
					}
					else if(image_file_format == image_file_format_cowraw)
					{
						image_format = IFSImageFactory::ImageFormat_RawCowFile;
					}
					else if (image_file_format == image_file_format_vhdx)
					{
						image_format = IFSImageFactory::ImageFormat_VHDX;
					}
					else if (image_file_format == image_file_format_vhdxz)
					{
						image_format = IFSImageFactory::ImageFormat_CompressedVHDX;
					}
					else //default
					{
						image_format = IFSImageFactory::ImageFormat_CompressedVHD;
					}

					if ((image_format == IFSImageFactory::ImageFormat_VHDX ||
						image_format == IFSImageFactory::ImageFormat_CompressedVHDX) &&
						drivesize + mbr_size > 64LL * 1024 * 1024 * 1024 * 1024)
					{
						ServerLogger::Log(logid, "Volume is too large for VHDX files with " + PrettyPrintBytes(drivesize + mbr_size) +
							". VHDX files have a maximum size of 64TiB. Please use another image file format.", LL_ERROR);
						goto do_image_cleanup;
					}

					if(!has_parent)
					{
						r_vhdfile=image_fak->createVHDFile(os_file_prefix(imagefn), false, drivesize+mbr_size,
							(unsigned int)vhd_blocksize*blocksize, true,
							image_format, server_settings->getSettings()->image_compress_threads);
					}
					else
					{
						r_vhdfile=image_fak->createVHDFile(os_file_prefix(imagefn), pParentvhd, false,
							true, image_format, drivesize + mbr_size,
							server_settings->getSettings()->image_compress_threads);
					}

					if(r_vhdfile==NULL || !r_vhdfile->isOpen())
					{
						ServerLogger::Log(logid, "Error opening VHD file \""+imagefn+"\"", LL_ERROR);
						goto do_image_cleanup;
					}

					hashfile=Server->openFile(os_file_prefix(imagefn+".hash"), MODE_WRITE);
					if(hashfile==NULL)
					{
						ServerLogger::Log(logid, "Error opening Hashfile \""+imagefn+".hash\"", LL_ERROR);
						goto do_image_cleanup;
					}
					
					if(transfer_bitmap)
					{
						bitmap_file.reset(Server->openFile(os_file_prefix(imagefn+".cbitmap"), MODE_RW_CREATE));
						if(bitmap_file.get()==NULL)
						{
							ServerLogger::Log(logid, "Error opening bitmap file \""+imagefn+".cbitmap\"", LL_ERROR);
							goto do_image_cleanup;
						}
						else
						{
							const char bitmap_magic[9] = "UrBBMM8C";
							bitmap_file->Write(bitmap_magic, 8);
						}
					}

					vhdfile=new ServerVHDWriter(r_vhdfile, blocksize, 5000, clientid, server_settings->getSettings()->use_tmpfiles_images,
						mbr_offset, hashfile, vhd_blocksize*blocksize, logid, drivesize + (int64)mbr_size);
					vhdfile_ticket = Server->getThreadPool()->execute(vhdfile, "image backup writer");

					blockdata=vhdfile->getBuffer();

					if(has_parent)
					{
						parenthashfile=Server->openFile(os_file_prefix(pParentvhd+".hash"), MODE_READ);
						if(parenthashfile==NULL)
						{
							ServerLogger::Log(logid, "Error opening Parenthashfile \""+pParentvhd+".hash\"", LL_ERROR);
							goto do_image_cleanup;
						}
						ServerStatus::setProcessTotalBytes(clientname, status_id, totalblocks*blocksize);
					}
					else
					{
						ServerStatus::setProcessTotalBytes(clientname, status_id, blockcnt*blocksize);
					}

					if (!disk_backup)
					{
						mbr_offset = writeMBR(vhdfile, drivesize);
						if (mbr_offset == 0)
						{
							ServerLogger::Log(logid, "Error writing image MBR", LL_ERROR);
							goto do_image_cleanup;
						}
						else
						{
							vhdfile->setMbrOffset(mbr_offset);
						}
					}
				}
				else
				{
					issmall=true;
				}
				csize+=+sizeof(int64);
				if(r>=csize)
				{
					memcpy(&blockcnt, &buffer[off], sizeof(int64) );
					blockcnt=little_endian(blockcnt);

					if (!has_parent || blockcnt<0)
					{
						int64 vhd_size = -1;
						if (has_parent && blockcnt < 0)
						{
							ServerLogger::Log(logid, "Change block tracking active. Max "+PrettyPrintBytes(-blockcnt*blocksize)+" have changed.", LL_INFO);
							vhd_size = mbr_offset + -blockcnt*blocksize;
						}
						else if (!has_parent && blockcnt>0)
						{
							r_vhdfile->setBackingFileSize(mbr_offset + blockcnt*blocksize);
							vhd_size = mbr_offset + blockcnt*blocksize;
						}

						if (vhd_size>0 && vhd_size >= 2040LL * 1024 * 1024 * 1024
							&& (image_file_format == image_file_format_vhd
								|| image_file_format == image_file_format_vhdz) )
						{
							ServerLogger::Log(logid, "Data on volume is too large for VHD files with " + PrettyPrintBytes(vhd_size) +
								". VHD files have a maximum size of 2040GB. Please use another image file format.", LL_ERROR);
							goto do_image_cleanup;
						}

						ServerStatus::setProcessTotalBytes(clientname, status_id, (blockcnt<0 ? -blockcnt : blockcnt)*blocksize);
					}

					off+=sizeof(int64);
				}
				else
				{
					issmall=true;
				}
				csize+=1;
				if(r>=csize)
				{
					image_flags=static_cast<unsigned char>(buffer[off]);
					if(image_flags & ImageFlag_Persistent)
						persistent=true;
					if(image_flags & ImageFlag_Bitmap)
						transfer_state = ETransferState_Bitmap;
					++off;
				}
				else
				{
					issmall=true;
				}

				unsigned int shadowdata_size=0;
				csize+=sizeof(unsigned int);
				if(r>=csize)
				{
					memcpy(&shadowdata_size, &buffer[off], sizeof(unsigned int));
					shadowdata_size = little_endian(shadowdata_size);
					off+=sizeof(unsigned int);
					if(shadowdata_size>0)
					{
						csize+= shadowdata_size;
						if( r>=csize)
						{
							std::string shadowdata(&buffer[off], shadowdata_size);
							readShadowData(shadowdata);
							off+= shadowdata_size;
						}
						else
						{
							issmall=true;
						}
					}
				}
				else
				{
					issmall=true;
				}

				csize+=sizeof(int);
				if(r>=csize)
				{
					int shadow_id;
					memcpy(&shadow_id, &buffer[off], sizeof(int));
					snapshot_id = little_endian(shadow_id);
					off+=sizeof(int);
				}
				else
				{
					issmall=true;
				}

				if(with_checksum)
				{
					csize+=sha_size;
					if(r>=csize)
					{
						sha256_init(&shactx);
						sha256_update(&shactx, (unsigned char*)&buffer[off_start], (unsigned int)csize-sha_size);
						unsigned char dig[sha_size];
						sha256_final(&shactx, dig);
						if(memcmp(dig, &buffer[off], sha_size)!=0)
						{
							ServerLogger::Log(logid, "Checksum for first packet wrong. Stopping image backup.", LL_ERROR);
							goto do_image_cleanup;
						}
						off+=sha_size;
						sha256_init(&shactx);
					}
					else
					{
						issmall=true;
					}
				}

				if(issmall)
				{
					ServerLogger::Log(logid, "First packet too small", LL_WARNING);
					off = off_start + static_cast<_u32>(r);
					continue;
				}

				curr_image_recv_timeout=image_recv_timeout_after_first;

				if(r==off)
				{
					off=0;
					continue;
				}
			}
			if(transfer_state==ETransferState_Bitmap)
			{
				size_t bitmap_size = static_cast<size_t>(totalblocks/8);
				if(totalblocks%8!=0)
				{
					++bitmap_size;
				}

				bitmap_size += sizeof(unsigned int); //bitmap blocksize
				
				if(bitmap_size>1024*1024*1024)
				{
					ServerLogger::Log(logid, "Bitmap too large", LL_ERROR);
					goto do_image_cleanup;
				}
				
				if(bitmap_read<bitmap_size)
				{
					size_t toread = (std::min)(bitmap_size - bitmap_read, r-off);
					sha256_update(&shactx, (unsigned char*)&buffer[off], (unsigned int)toread);
				}
				
				bitmap_size += sha_size;
				
				size_t toread = (std::min)(bitmap_size - bitmap_read, r-off);
				
				if(bitmap_file.get()!=NULL 
					&& bitmap_file->Write(&buffer[off], static_cast<_u32>(toread))!=toread)
				{
					ServerLogger::Log(logid, "Error writing to bitmap file", LL_ERROR);
					goto do_image_cleanup;
				}
				
				bitmap_read+=toread;
				
				if(bitmap_read==bitmap_size)
				{
					off+=static_cast<_u32>(toread);
					
					if (bitmap_file.get() != NULL)
					{
						bitmap_file->Seek(8 + bitmap_size - sha_size);
						char dig_recv[sha_size];
						bitmap_file->Read(dig_recv, sha_size);

						unsigned char dig[sha_size];
						sha256_final(&shactx, dig);

						if (memcmp(dig, dig_recv, sha_size) != 0)
						{
							ServerLogger::Log(logid, "Checksum for bitmap wrong. Stopping image backup.", LL_ERROR);
							goto do_image_cleanup;
						}
					}
					
					sha256_init(&shactx);
					transfer_state = ETransferState_Image;
					transfer_bitmap = false;
					bitmap_file.reset();
				}
				else
				{
				    assert(r==off+toread);
				    off=0;
				    continue;
				}
			}
			while(true)
			{
				if(blockleft==0)
				{
					if(currblock!=-1) // write current block
					{
						if(nextblock<=currblock)
						{
							++numblocks;
							int64 ctime=Server->getTimeMS();
							if(ctime-last_status_update>status_update_intervall)
							{
								last_status_update = ctime;
								if(blockcnt!=0)
								{
									if(has_parent && blockcnt>0)
									{
										ServerStatus::setProcessDoneBytes(clientname, status_id, currblock*blocksize);
										ServerStatus::setProcessPcDone(clientname, status_id, 
											(int)(((double)currblock/(double)totalblocks)*100.0+0.5) );
									}
									else
									{
										ServerStatus::setProcessDoneBytes(clientname, status_id, numblocks*blocksize);
										ServerStatus::setProcessPcDone(clientname, status_id,
											(int)(((double)numblocks/(double)((blockcnt>0 ? blockcnt : -blockcnt)))*100.0+0.5) );
									}
								}
							}

							if(ctime- last_eta_update>eta_update_intervall)
							{
								last_eta_update = ctime;

								int64 rel_blocks = (has_parent && blockcnt>=0) ? currblock : numblocks;
								if (rel_blocks > 1000)
								{									
									int64 new_blocks = rel_blocks - last_eta_update_blocks;
									int64 passed_time = ctime - eta_set_time;

									if(new_blocks>0 && passed_time>0)
									{
										last_eta_update_blocks = rel_blocks;										
										
										eta_set_time = Server->getTimeMS();

										double speed_bpms = static_cast<double>(new_blocks) / passed_time;

										bool set_eta = false;
										if(eta_estimated_speed==0)
										{
											eta_estimated_speed = speed_bpms;
										}
										else if (eta_set_time - image_backup_starttime < 5 * 60 * 1000)
										{
											eta_estimated_speed = 0.9*eta_estimated_speed + 0.1*speed_bpms;
										}
										else
										{
											eta_estimated_speed = 0.99*eta_estimated_speed + 0.01*speed_bpms;
											set_eta = true;
										}

										if (set_eta)
										{
											int64 remaining_blocks = ((has_parent && blockcnt >= 0) ? totalblocks : 
												((blockcnt>0 ? blockcnt : -blockcnt)) - rel_blocks);
											ServerStatus::setProcessEta(clientname, status_id,
												static_cast<int64>(remaining_blocks / speed_bpms + 0.5), eta_set_time);
										}
									}									
								}
							}

							if (ctime - speed_set_time > 10000)
							{
								transferred_bytes += cc->getTransferedBytes();
								cc->resetTransferedBytes();

								int64 new_bytes = transferred_bytes - last_speed_received_bytes;
								int64 passed_time = ctime - speed_set_time;

								if (passed_time > 0)
								{
									speed_set_time = ctime;

									double speed_bpms = static_cast<double>(new_bytes) / passed_time;

									if (last_speed_received_bytes > 0)
									{
										ServerStatus::setProcessSpeed(clientname, status_id,
											speed_bpms);
									}

									last_speed_received_bytes = transferred_bytes;
								}
							}

							nextblock=updateNextblock(nextblock, currblock, &shactx, zeroblockdata.data(),
								has_parent, hashfile, parenthashfile,
								blocksize, mbr_offset, vhd_blocksize, warned_about_parenthashfile_error,
								-1, vhdfile, 0);

							sha256_update(&shactx, (unsigned char *)blockdata, blocksize);

							vhdfile->writeBuffer(mbr_offset+currblock*blocksize, blockdata, blocksize);
							blockdata=vhdfile->getBuffer();

							if(nextblock%vhd_blocksize==0 && nextblock!=0)
							{
								//Server->Log("Hash written "+convert(currblock), LL_DEBUG);
								sha256_final(&shactx, verify_checksum);
								hashfile->Write((char*)verify_checksum, sha_size);
								sha256_init(&shactx);
							}

							if(vhdfile->hasError())
							{
								ServerLogger::Log(logid, "FATAL ERROR: Could not write to VHD-File", LL_ERROR);
								ClientMain::sendMailToAdmins("Fatal error occurred during image backup", ServerLogger::getWarningLevelTextLogdata(logid));
								goto do_image_cleanup;
							}
						}
						else if(nextblock-currblock>vhd_blocksize)
						{
							if (num_hash_errors < max_num_hash_errors)
							{
								ServerLogger::Log(logid, "Block sent out of sequence. Expected block >=" + convert(nextblock - vhd_blocksize - 1) + " got " + convert(currblock) + ". Retrying...", LL_WARNING);
								transferred_bytes += cc->getTransferedBytes();
								Server->destroy(cc);
								cc = NULL;
								nextblock = last_verified_block;
								hashfile->Seek((nextblock / vhd_blocksize)*sha_size);
								++num_hash_errors;
								break;
							}
							else
							{
								ServerLogger::Log(logid, "Block sent out of sequence. Expected block >=" + convert(nextblock - vhd_blocksize - 1) + " got " + convert(currblock) + ". Stopping backup.", LL_ERROR);
								goto do_image_cleanup;
							}
						}

						currblock=-1;
					}
					bool accum=false;
					if(r-off>=sizeof(int64) )
					{
						memcpy(&currblock, &buffer[off], sizeof(int64) );
						currblock = little_endian(currblock);
						if(currblock==-123)
						{
							ServerStatus::setProcessPcDone(clientname, status_id,
								100);
							ServerStatus::setProcessEta(clientname, status_id, -1);
							ServerStatus::setProcessSpeed(clientname, status_id, 0);

							if(nextblock<=totalblocks)
							{
								nextblock=updateNextblock(nextblock, totalblocks, &shactx, zeroblockdata.data(), has_parent,
									hashfile, parenthashfile, blocksize, mbr_offset, vhd_blocksize, warned_about_parenthashfile_error,
									-1, vhdfile, 0);

								if(nextblock!=0)
								{
									//Server->Log("Hash written "+convert(nextblock), LL_INFO);
									unsigned char dig[sha_size];
									sha256_final(&shactx, dig);
									hashfile->Write((char*)dig, sha_size);
								}
							}

							if(cc!=NULL)
							{
								transferred_bytes+=cc->getTransferedBytes();
								transferred_bytes_real+=cc->getRealTransferredBytes();
								Server->destroy(cc);
							}
							

							if(vhdfile!=NULL)
							{
								vhdfile->freeBuffer(blockdata);
							}
							if(parenthashfile!=NULL) Server->destroy(parenthashfile);

							bool vhdfile_err=false;

							if(vhdfile!=NULL)
							{
								if(!pParentvhd.empty() &&
									image_file_format == image_file_format_cowraw)
								{
									vhdfile->setDoTrim(true);
								}

								if(synthetic_full && image_file_format!=image_file_format_cowraw)
								{
									vhdfile->setDoMakeFull(true);
								}

								vhdfile->doExit();
								Server->getThreadPool()->waitFor(vhdfile_ticket);
								vhdfile_err=vhdfile->hasError();
								delete vhdfile;
								vhdfile=NULL;
							}

							if(hashfile!=NULL) Server->destroy(hashfile);

							IFile *t_file=Server->openFile(os_file_prefix(imagefn), MODE_READ);
							if(t_file!=NULL)
							{
								int64 image_size = t_file->RealSize();
								Server->destroy(t_file);

								std::unique_ptr<IFile> sync_f;
								if (!vhdfile_err)
								{
									if (!os_sync(imagefn))
									{
										ServerLogger::Log(logid, "Syncing file system failed. Image backup may not be completely on disk. " + os_last_error_str(), BackupServer::canSyncFs() ? LL_ERROR : LL_DEBUG);

										if(BackupServer::canSyncFs())
											vhdfile_err = true;
									}

									if(!vhdfile_err)
									{
										sync_f.reset(Server->openFile(os_file_prefix(imagefn + ".sync"), MODE_WRITE));

										if (sync_f.get() != NULL
											&& !sync_f->Sync())
										{
											ServerLogger::Log(logid, "Error syncing sync file to disk. " + os_last_error_str(), LL_ERROR);
											vhdfile_err = true;
										}
									}									
								}

								db->BeginWriteTransaction();
								backup_dao->setImageSize(image_size, backupid);
								backup_dao->addImageSizeToClient(clientid, image_size);
								if(!vhdfile_err)
								{
									if ( dependencies.empty()
										 && set_complete 
										&& sync_f.get()!=NULL)
									{
										backup_dao->setImageBackupComplete(backupid);
									}
									if (sync_f.get() != NULL)
									{
										backup_dao->setImageBackupSynctime(backupid);
									}
									else
									{
										ServerLogger::Log(logid, "Error creating sync file at " + imagefn + ".sync", LL_ERROR);
										vhdfile_err = true;
									}
									db->EndTransaction();

									if (image_file_format == image_file_format_cowraw
										&& BackupServer::getSnapshotMethod(true)!= BackupServer::ESnapshotMethod_None)
									{
										if(!SnapshotHelper::makeReadonly(true, clientname, backuppath_single))
										{
											ServerLogger::Log(logid, "Making image backup snapshot read only failed", LL_WARNING);
										}
									}
								}
								else
								{
									db->EndTransaction();
								}
							}

							running_updater->stop();
							backup_dao->updateImageBackupRunning(backupid);

							int64 passed_time=Server->getTimeMS()-image_backup_starttime;
							if(passed_time==0) passed_time=1;

							ServerLogger::Log(logid, "Transferred "+PrettyPrintBytes(transferred_bytes)+" - Average speed: "+PrettyPrintSpeed((size_t)((transferred_bytes*1000)/(passed_time)) ), LL_INFO );
							if(transferred_bytes_real>0)
							{
								ServerLogger::Log(logid, "(Before compression: "+PrettyPrintBytes(transferred_bytes_real)+" ratio: "+convert((float)transferred_bytes_real/transferred_bytes)+")");
							}

							if (!runPostBackupScript(!pParentvhd.empty() && !synthetic_full && incremental != 0,
								imagefn, pLetter, !vhdfile_err))
							{
								return false;
							}

							return !vhdfile_err;
						}
						else if(currblock==-124 ||
							currblock==0xFFFFFFFFFFFFFFFFLLU)
						{
							if(r-off>sizeof(int64))
							{
								std::string err;
								err.resize(r-off-sizeof(int64) );
								memcpy(&err[0], &buffer[off+sizeof(int64)], r-off-sizeof(int64));

								if(err.find("|#|")!=std::string::npos)
								{
									err=getuntil("|#|", err);
								}

								if (next(err, 0, "Ident reset"))
								{
									client_main->forceReauthenticate();
								}

								ServerLogger::Log(logid, "Error on client occurred: "+err, LL_ERROR);
							}
							Server->destroy(cc);
							if(vhdfile!=NULL)
							{
								vhdfile->freeBuffer(blockdata);
								vhdfile->doExitNow();
								std::vector<THREADPOOL_TICKET> wf;wf.push_back(vhdfile_ticket);
								Server->getThreadPool()->waitFor(wf);
								delete vhdfile;
								vhdfile=NULL;
							}
							if(hashfile!=NULL) Server->destroy(hashfile);
							if(parenthashfile!=NULL) Server->destroy(parenthashfile);
							running_updater->stop();
							return false;
						}
						else if(currblock==-125) //ping
						{
							off+=sizeof(int64);
							currblock=-1;
						}
						else if(currblock==-126) //checksum
						{
							if(r-off>=2*sizeof(int64)+sha_size)
							{
								int64 hblock;
								unsigned char dig[sha_size];
								memcpy(&hblock, &buffer[off+sizeof(int64)], sizeof(int64));
								hblock = little_endian(hblock);
								memcpy(&dig, &buffer[off+2*sizeof(int64)], sha_size);


								if( (nextblock<hblock || (hblock==blocks && nextblock%vhd_blocksize!=0) ) && hblock>0)
								{
									if(nextblock<hblock)
									{
										nextblock=updateNextblock(nextblock, hblock-1, &shactx, zeroblockdata.data(), has_parent,
											hashfile, parenthashfile, blocksize, mbr_offset,
											vhd_blocksize, warned_about_parenthashfile_error, -1, vhdfile, 1);
										sha256_update(&shactx, zeroblockdata.data(), blocksize);						
									}
									if( (nextblock%vhd_blocksize==0 || hblock==blocks) && nextblock!=0)
									{
										sha256_final(&shactx, verify_checksum);
										hashfile->Write((char*)verify_checksum, sha_size);
										sha256_init(&shactx);
									}
								}

								if( memcmp(verify_checksum, dig, sha_size)!=0)
								{
									Server->Log("Client hash="+base64_encode(dig, sha_size)+" Server hash="+base64_encode(verify_checksum, sha_size)+" hblock="+convert(hblock), LL_DEBUG);
									if(num_hash_errors<max_num_hash_errors)
									{
										ServerLogger::Log(logid, "Checksum for image block wrong. Retrying...", LL_WARNING);
										transferred_bytes+=cc->getTransferedBytes();
										Server->destroy(cc);
										cc=NULL;
										nextblock=last_verified_block;
										hashfile->Seek((nextblock / vhd_blocksize)*sha_size);
										++num_hash_errors;
										break;
									}
									else
									{
										ServerLogger::Log(logid, "Checksum for image block wrong. Stopping image backup.", LL_ERROR);
										goto do_image_cleanup;
									}
								}
								else
								{
									if(hblock>=vhd_blocksize)
									{
										last_verified_block=hblock-vhd_blocksize;
									}
									else
									{
										last_verified_block=hblock;
									}
								}

								off+=2*sizeof(int64)+sha_size;								
							}
							else
							{
								accum=true;
							}
							currblock=-1;
						}
						else if(currblock==-127) //Empty VHD block
						{
							if(r-off>=2*sizeof(int64))
							{
								int64 vhdblock;
								memcpy(&vhdblock, &buffer[off+sizeof(int64)], sizeof(int64));
								vhdblock = little_endian(vhdblock);
								nextblock = updateNextblock(nextblock, vhdblock+vhd_blocksize, &shactx, zeroblockdata.data(), has_parent,
									hashfile, parenthashfile, blocksize, mbr_offset, vhd_blocksize, warned_about_parenthashfile_error,
									vhdblock, vhdfile, 0);
								off += sizeof(int64);
							}
							else
							{
								accum=true;
							}
							currblock=-1;
						}
						else if (currblock == -128) //Number of skipped blocks
						{
							if (r - off >= 2 * sizeof(int64))
							{
								int64 zero_skipped;
								memcpy(&zero_skipped, &buffer[off + sizeof(int64)], sizeof(int64));
								zero_skipped = little_endian(zero_skipped);
								numblocks += zero_skipped;
								off += 2*sizeof(int64);
							}
							else
							{
								accum = true;
							}
							currblock = -1;
						}
						else if(currblock<0
							|| currblock>totalblocks)
						{
							if (num_hash_errors < max_num_hash_errors)
							{
								ServerLogger::Log(logid, "Received unknown block number: " + convert(currblock) + " (max: "+convert(totalblocks)+"). Retrying...", LL_WARNING);
								transferred_bytes += cc->getTransferedBytes();
								Server->destroy(cc);
								cc = NULL;
								nextblock = last_verified_block;
								hashfile->Seek((nextblock / vhd_blocksize)*sha_size);
								++num_hash_errors;
								break;
							}
							else
							{
								ServerLogger::Log(logid, "Received unknown block number: " + convert(currblock) + +" (max: " + convert(totalblocks) + "). Stopping backup.", LL_ERROR);
								goto do_image_cleanup;
							}
						}
						else
						{
							off+=sizeof(int64);
							blockleft=blocksize;
						}
					}
					else
					{
						accum=true;
					}

					if(accum)
					{
						if(r-off>0)
						{
							memmove(buffer.data(), &buffer[off], r-off);
							off=(_u32)r-off;
							break;
						}
						else
						{
							off=0;
							break;
						}
					}
				}
				else
				{
					unsigned int available=(std::min)(blockleft, (unsigned int)r-off);
					if (available > 0)
					{
						memcpy(&blockdata[blocksize - blockleft], &buffer[off], available);
						blockleft -= available;
						off += available;
					}

					if( off>=r
						&& blockleft>0
						&& currblock!=-1)
					{
						off=0;
						break;
					}
				}
			}
		}
	}
	has_timeout_error = true;
	ServerLogger::Log(logid, "Timeout while transfering image data", LL_ERROR);

do_image_cleanup:

	ServerStatus::setProcessSpeed(clientname, status_id, 0);

	if(cc!=NULL)
	{
		transferred_bytes+=cc->getTransferedBytes();
	}
	int64 passed_time=Server->getTimeMS()-image_backup_starttime;
	if(passed_time==0) passed_time=1;
	ServerLogger::Log(logid, "Transferred "+PrettyPrintBytes(transferred_bytes)+" - Average speed: "+PrettyPrintSpeed((size_t)((transferred_bytes*1000)/(passed_time) )), LL_INFO );
	if(transferred_bytes_real>0)
	{
		ServerLogger::Log(logid, "(Before compression: "+PrettyPrintBytes(transferred_bytes_real)+" ratio: "+convert((float)transferred_bytes_real/transferred_bytes)+")");
	}
	if(cc!=NULL)
		Server->destroy(cc);

	running_updater->stop();

	runPostBackupScript(!pParentvhd.empty() && !synthetic_full && incremental!=0, imagefn, pLetter, false);

	if(vhdfile!=NULL)
	{
		if(blockdata!=NULL)
			vhdfile->freeBuffer(blockdata);

		vhdfile->doExitNow();
		Server->getThreadPool()->waitFor(vhdfile_ticket);
		delete vhdfile;
		vhdfile=NULL;
	}
	else if(r_vhdfile!=NULL)
	{
		image_fak->destroyVHDFile(r_vhdfile);
	}
	if(hashfile!=NULL) Server->destroy(hashfile);
	if(parenthashfile!=NULL) Server->destroy(parenthashfile);
	return false;
}

unsigned int ImageBackup::writeMBR(ServerVHDWriter* vhdfile, uint64 volsize)
{
	unsigned char *mbr=(unsigned char *)vhdfile->getBuffer();
	if(mbr==NULL)
		return 0;

	unsigned char *mptr=mbr;

	memcpy(mptr, mbr_code, 440);
	mptr+=440;
	int sig=Server->getRandomNumber();
	memcpy(mptr, &sig, sizeof(int));
	mptr+=sizeof(int);
	*mptr=0;
	++mptr;
	*mptr=0;
	++mptr;

	unsigned char partition[16];
	partition[0]=0x80;
	partition[1]=0xfe;
	partition[2]=0xff;
	partition[3]=0xff;
	partition[4]=0x07; //ntfs
	partition[5]=0xfe;
	partition[6]=0xff;
	partition[7]=0xff;
	partition[8]=0x00;
	partition[9]=0x04;
	partition[10]=0x00;
	partition[11]=0x00;

	unsigned int sectors=(unsigned int)(volsize/((uint64)sector_size));
	sectors = little_endian(sectors);
	memcpy(&partition[12], &sectors, sizeof(unsigned int) );

	memcpy(mptr, partition, 16);
	mptr+=16;
	for(int i=0;i<3;++i)
	{
		memset(mptr, 0, 16);
		mptr+=16;
	}
	*mptr=0x55;
	++mptr;
	*mptr=0xaa;
	vhdfile->writeBuffer(0, (char*)mbr, 512);

	for(int i=0;i<1023;++i)
	{
		char *buf=vhdfile->getBuffer();
		if(buf==NULL)
			return 0;

		memset(buf, 0, 512);
		vhdfile->writeBuffer((i+1)*512, buf, 512);
	}

	return 1024*512;
}

int64 ImageBackup::updateNextblock(int64 nextblock, int64 currblock, sha256_ctx *shactx, unsigned char *zeroblockdata, bool parent_fn,
	IFile *hashfile, IFile *parenthashfile, unsigned int blocksize,
	int64 mbr_offset, int64 vhd_blocksize, bool& warned_about_parenthashfile_error, int64 empty_vhdblock_start,
	ServerVHDWriter* vhdfile, int64 trim_add)
{
	if(trim_add>0
		&& vhdfile != NULL
		&& parent_fn
	    && (nextblock==currblock) )
	{
		vhdfile->writeBuffer(mbr_offset + nextblock*blocksize, NULL,
			    static_cast<unsigned int>(trim_add*blocksize));
	}
	
	if(nextblock==currblock)
		return nextblock+1;
	else if(nextblock>currblock)
		return nextblock;

	if(currblock-nextblock>=vhd_blocksize)
	{
		if(nextblock%vhd_blocksize!=0)
		{
			int64 trim_start_block = -1;

			while(true)
			{
				if (trim_start_block == -1)
				{
					trim_start_block = nextblock;
				}

				sha256_update(shactx, zeroblockdata, blocksize);
				++nextblock;

				if(nextblock%vhd_blocksize==0 && nextblock!=0)
				{
					unsigned char dig[sha_size];
					sha256_final(shactx, dig);
					hashfile->Write((char*)dig, sha_size);
					sha256_init(shactx);
					break;
				}
			}

			if (trim_start_block != -1
				&& vhdfile!=NULL
				&& parent_fn )
			{
				vhdfile->writeBuffer(mbr_offset + trim_start_block*blocksize, NULL,
					static_cast<unsigned int>((nextblock - trim_start_block)*blocksize));
			}
		}

		while(currblock-nextblock>=vhd_blocksize)
		{
			if(!parent_fn || nextblock==empty_vhdblock_start)
			{
				hashfile->Write((char*)zero_hash, sha_size);
			}
			else
			{
				bool b=parenthashfile->Seek((nextblock/vhd_blocksize)*sha_size);
				if(!b)
				{
					if(!warned_about_parenthashfile_error)
					{
						Server->Log("Seeking in parent hash file failed (may be caused by a volume with increased size)", LL_WARNING);
						warned_about_parenthashfile_error=true;
					}
					hashfile->Write((char*)zero_hash, sha_size);
				}
				else
				{
					char dig[sha_size];
					_u32 rc=parenthashfile->Read(dig, sha_size);
					if(rc!=sha_size)
					{
						if(!warned_about_parenthashfile_error)
						{
							Server->Log("Reading from parent hash file failed (may be caused by a volume with increased size)", LL_WARNING);
							warned_about_parenthashfile_error=true;
						}
						hashfile->Write((char*)zero_hash, sha_size);
					}
					else
					{
						hashfile->Write(dig, sha_size);
					}
				}
			}
			nextblock+=vhd_blocksize;
		}
	}

	int64 trim_start_block = -1;

	while(nextblock<currblock)
	{
		if (trim_start_block == -1)
		{
			trim_start_block = nextblock;
		}

		sha256_update(shactx, zeroblockdata, blocksize);
		++nextblock;
		if(nextblock%vhd_blocksize==0 && nextblock!=0)
		{
			unsigned char dig[sha_size];
			sha256_final(shactx, dig);
			hashfile->Write((char*)dig, sha_size);
			sha256_init(shactx);
		}
	}
	
	if(trim_add>0
	    && trim_start_block==-1)
	{
		trim_start_block = nextblock;
	}

	if (trim_start_block != -1
		&& vhdfile!=NULL
		&& parent_fn )
	{
		vhdfile->writeBuffer(mbr_offset + trim_start_block*blocksize, NULL,
			static_cast<unsigned int>((nextblock - trim_start_block + trim_add)*blocksize));
	}

	return nextblock+1;
}

std::string ImageBackup::constructImagePath(const std::string &letter, std::string image_file_format, std::string pParentvhd)
{
	bool full_backup = pParentvhd.empty();

	if (!createDirectoryForClient())
	{
		return std::string();
	}

	time_t tt=time(NULL);
#ifdef _WIN32
	tm lt;
	tm *t=&lt;
	localtime_s(t, &tt);
#else
	tm *t=localtime(&tt);
#endif
	char buffer[500];
	strftime(buffer, 500, "%y%m%d-%H%M", t);
	std::string backupfolder_uncompr=server_settings->getSettings()->backupfolder_uncompr;
	backuppath_single = std::string(buffer) + "_Image_" + letter;
	std::string image_folder = backupfolder_uncompr + os_file_sep() + clientname + os_file_sep() + backuppath_single;
	std::string imgpath = image_folder + os_file_sep() + "Image_"+letter+"_"+(std::string)buffer;
	bool create_folder = true;
	if(image_file_format==image_file_format_vhd)
	{
		imgpath+=".vhd";
	}
	else if (image_file_format == image_file_format_vhdx)
	{
		imgpath += ".vhdx";
	}
	else if (image_file_format == image_file_format_vhdxz)
	{
		imgpath += ".vhdxz";
	}
	else if(image_file_format==image_file_format_cowraw)
	{
		imgpath+=".raw";
		create_folder = false;
		if (BackupServer::getSnapshotMethod(true) == BackupServer::ESnapshotMethod_None
			&& BackupServer::canReflink())
		{
			create_folder = true;
		}
		else if (full_backup)
		{
			if (BackupServer::getSnapshotMethod(true) == BackupServer::ESnapshotMethod_Zfs)
			{
				std::unique_ptr<IFile> touch_f(Server->openFile(image_folder, MODE_WRITE));
				if (touch_f.get()==NULL)
				{
					ServerLogger::Log(logid, "Could not touch file " + image_folder + ". " + os_last_error_str(), LL_ERROR);
					return std::string();
				}
				touch_f->Sync();
			}

			std::string errmsg;
			if (!SnapshotHelper::createEmptyFilesystem(true, clientname, backuppath_single, errmsg))
			{
				if (BackupServer::getSnapshotMethod(true) == BackupServer::ESnapshotMethod_Zfs)
				{
					Server->deleteFile(image_folder);
				}
				errmsg = trim(errmsg);
				ServerLogger::Log(logid, "Error creating empty image subvolume. "
					+ (errmsg.empty() ? "" : ("\"" + errmsg + "\"")), LL_ERROR);
				return std::string();
			}
			else if (BackupServer::getSnapshotMethod(true) == BackupServer::ESnapshotMethod_Zfs)
			{
				std::string mountpoint = SnapshotHelper::getMountpoint(true, clientname, backuppath_single);
				if (mountpoint.empty())
				{
					ServerLogger::Log(logid, "Could not find mountpoint of snapshot of client " + clientname + " path " + backuppath_single, LL_ERROR);
					return std::string();
				}

				if (!os_link_symbolic(mountpoint, image_folder+"_new"))
				{
					ServerLogger::Log(logid, "Could create symlink to mountpoint at " + image_folder + " to " + mountpoint+". "+os_last_error_str(), LL_ERROR);
					return std::string();
				}

				if (!os_rename_file(image_folder + "_new", image_folder))
				{
					ServerLogger::Log(logid, "Could rename symlink at " + image_folder + "_new to " + image_folder + ". " + os_last_error_str(), LL_ERROR);
					return std::string();
				}
			}
		}
	}
	else
	{
		imgpath+=".vhdz";
	}

	if (create_folder)
	{
		if (!os_create_dir(os_file_prefix(image_folder)))
		{
			return std::string();
		}
	}

	if (!full_backup && image_file_format == image_file_format_cowraw)
	{
		std::string parent_backuppath_single = ExtractFileName(ExtractFilePath(pParentvhd));
		std::string parent_fn = ExtractFileName(pParentvhd);

		if (BackupServer::getSnapshotMethod(true) == BackupServer::ESnapshotMethod_None
			&& BackupServer::canReflink() )
		{
			if (!os_create_hardlink(imgpath, pParentvhd,
				true, NULL))
			{
				ServerLogger::Log(logid, "Could not reflink \"" + pParentvhd + "\" to \""
					+ imgpath + "\". "+os_last_error_str(), LL_ERROR);
				return std::string();
			}

			if (!os_create_hardlink(imgpath+".bitmap", pParentvhd+".bitmap",
				true, NULL))
			{
				ServerLogger::Log(logid, "Could not reflink \"" + pParentvhd + ".bitmap\" to \""
					+ imgpath + ".bitmap\". " + os_last_error_str(), LL_ERROR);
				return std::string();
			}
		}
		else
		{
			if (BackupServer::getSnapshotMethod(true) == BackupServer::ESnapshotMethod_Zfs)
			{
				std::unique_ptr<IFile> touch_f(Server->openFile(image_folder, MODE_WRITE));
				if (touch_f.get() == NULL)
				{
					ServerLogger::Log(logid, "Could not touch file " + image_folder + ". " + os_last_error_str(), LL_ERROR);
					return std::string();
				}
				touch_f->Sync();
			}

			ServerLogger::Log(logid, "Creating writable snapshot of previous image backup...", LL_INFO);
			std::string errmsg;
			if (!SnapshotHelper::snapshotFileSystem(true, clientname, parent_backuppath_single, backuppath_single, errmsg))
			{
				errmsg = trim(errmsg);
				ServerLogger::Log(logid, "Could not create snapshot of previous image backup at " + parent_backuppath_single
					+ (errmsg.empty() ? "" : (" \"" + errmsg + "\"")), LL_ERROR);
				if (BackupServer::getSnapshotMethod(true) == BackupServer::ESnapshotMethod_Zfs)
				{
					Server->deleteFile(image_folder);
				}
				return std::string();
			}
			else
			{
				if (BackupServer::getSnapshotMethod(true) == BackupServer::ESnapshotMethod_Zfs)
				{
					std::string mountpoint = SnapshotHelper::getMountpoint(true, clientname, backuppath_single);
					if (mountpoint.empty())
					{
						ServerLogger::Log(logid, "Could not find mountpoint of snapshot of client " + clientname + " path " + backuppath_single, LL_ERROR);
						return std::string();
					}

					if (!os_link_symbolic(mountpoint, image_folder + "_new"))
					{
						ServerLogger::Log(logid, "Could create symlink to mountpoint at " + image_folder + " to " + mountpoint + ". " + os_last_error_str(), LL_ERROR);
						return std::string();
					}

					if (!os_rename_file(image_folder + "_new", image_folder))
					{
						ServerLogger::Log(logid, "Could rename symlink at " + image_folder + "_new to " + image_folder + ". " + os_last_error_str(), LL_ERROR);
						return std::string();
					}
				}

				Server->deleteFile(image_folder + os_file_sep() + parent_fn + ".hash");
				Server->deleteFile(image_folder + os_file_sep() + parent_fn + ".cbitmap");
				Server->deleteFile(image_folder + os_file_sep() + parent_fn + ".mbr");
				Server->deleteFile(image_folder + os_file_sep() + parent_fn + ".sync");
				os_rename_file(image_folder + os_file_sep() + parent_fn + ".bitmap", imgpath + ".bitmap");
				if (!os_rename_file(image_folder + os_file_sep() + parent_fn, imgpath))
				{
					ServerLogger::Log(logid, "Error renaming in snapshot (\"" + image_folder + os_file_sep() + parent_fn + "\" to \"" + imgpath + "\")", LL_ERROR);
					return std::string();
				}
			}
		}
	}

	int64 free_space = os_free_space(os_file_prefix(ExtractFilePath(imgpath)));
	if (free_space != -1 && free_space<minfreespace_image)
	{
		ServerLogger::Log(logid, "Not enough free space. Cleaning up.", LL_INFO);
		if (!ServerCleanupThread::cleanupSpace(minfreespace_image))
		{
			ServerLogger::Log(logid, "Could not free space for image. NOT ENOUGH FREE SPACE.", LL_ERROR);
			return std::string();
		}
	}

	return imgpath;
}

SBackup ImageBackup::getLastImage(const std::string &letter, bool incr)
{
	ServerBackupDao::SImageBackup image_backup;
	if(incr)
	{
		image_backup = backup_dao->getLastImage(clientid, client_main->getCurrImageVersion(), letter);
	}
	else
	{
		image_backup = backup_dao->getLastFullImage(clientid, client_main->getCurrImageVersion(), letter);
	}

	if(image_backup.exists)
	{
		SBackup b;
		b.incremental=image_backup.incremental;
		b.path=image_backup.path;
		b.incremental_ref=static_cast<int>(image_backup.id);
		b.backup_time_ms=image_backup.duration;
		return b;
	}
	else
	{
		SBackup b;
		b.incremental=-2;
		b.incremental_ref=0;
		return b;
	}
}

std::string ImageBackup::getMBR(const std::string &dl, const std::string& disk_path,
	bool image_full, int64 snapshot_id, bool& fatal_error, std::string& loadfn)
{
	fatal_error = true;

	std::string params = "driveletter=" + EscapeParamString(dl);

	if (!clientsubname.empty())
	{
		params += "&clientsubname=" + EscapeParamString(clientsubname);
	}

	params += "&disk_path=" + EscapeParamString(disk_path);

	if (snapshot_id != 0)
	{
		params += "&shadowid=" + convert(snapshot_id);
	}

	params += std::string("&image_full=") + (image_full ? "1" : "0");
	params += "&running_jobs=" + convert(ServerStatus::numRunningJobs(clientname));
	params += "&token=" + EscapeParamString(server_token);

	std::string ret=client_main->sendClientMessage("MBR "+ params, "Getting MBR for drive "+dl+" failed", 10000);
	CRData r(&ret);
	char b;
	if(r.getChar(&b) && b==1 )
	{
		char ver;
		if(r.getChar(&ver) )
		{
			std::string zipfn;
			if(ver!=0 && ver!=1
				&& ver!=100)
			{
				ServerLogger::Log(logid, "MBR version "+convert((int)ver)+" is not supported by this server", LL_ERROR);
			}
			else if (ver == 100
				&& r.getStr2(&zipfn))
			{
				ServerLogger::Log(logid, "Loading ZIP metadata from "+zipfn, LL_INFO);
				loadfn = zipfn;
				return ret.substr(0,2);
			}
			else
			{
				CRData r2(&ret);
				SMBRData mbrdata(r2);
				if(!mbrdata.errmsg.empty())
				{
					ServerLogger::Log(logid, "During getting MBR: "+mbrdata.errmsg, LL_WARNING);
				}
				return ret;
			}
		}
		else
		{
			ServerLogger::Log(logid, "Could not read version information in MBR", LL_ERROR);
		}
	}
	else if(dl!="SYSVOL" && dl!="ESP")
	{
		std::string errmsg;
		if( r.getStr(&errmsg) && !errmsg.empty())
		{
			if (errmsg.find("dynamic volume")!=std::string::npos)
			{
				fatal_error = false;
			}

			errmsg=". Error message: "+errmsg;
		}
		ServerLogger::Log(logid, "Could not read MBR"+errmsg, LL_ERROR);
	}

	return "";
}

bool ImageBackup::runPostBackupScript(bool incr, const std::string& path, const std::string &pLetter, bool success)
{
	std::string script_name;
	if (!incr)
	{
		script_name = "post_full_imagebackup";
	}
	else
	{
		script_name = "post_incr_imagebackup";
	}

	return ClientMain::run_script("urbackup" + os_file_sep() + script_name,
		"\""+ path+"\" \"" + pLetter + "\" " + (success ? "1" : "0"), logid);
}

bool ImageBackup::addBackupToDatabase(const std::string &pLetter, const std::string &pParentvhd, int incremental, int incremental_ref,
	const std::string& imagefn, ScopedLockImageFromCleanup& cleanup_lock, ServerRunningUpdater *running_updater)
{
	if (imagefn.empty())
	{
		return true;
	}

	if (backup_starttime <= 0)
	{
		backup_starttime = Server->getTimeSeconds();
	}

	if (pParentvhd.empty())
	{
		if (!backup_dao->newImageBackup(clientid, imagefn, 0, 0, client_main->getCurrImageVersion(), pLetter, backup_starttime))
		{
			return false;
		}
	}
	else
	{
		if (!backup_dao->newImageBackup(clientid, imagefn, synthetic_full ? 0 : incremental, incremental_ref, client_main->getCurrImageVersion(), pLetter, backup_starttime))
		{
			return false;
		}
	}

	backupid = static_cast<int>(db->getLastInsertID());
	cleanup_lock.reset(backupid);
	if (running_updater != NULL)
	{
		running_updater->setBackupid(backupid);
	}

	return true;
}

bool ImageBackup::readShadowData(const std::string & shadowdata)
{
	CRData rdata(shadowdata.data(), shadowdata.size());
	char version=-1;
	if (!rdata.getChar(&version) || version != 0)
	{
		if (version != 92) //sent by clients < version 2.1
		{
			ServerLogger::Log(logid, "Unknown shadow data version: " + convert(version), LL_ERROR);
		}
		return false;
	}

	IScopedLock lock(mutex.get());

	snapshot_group_loginfo.clear();
	dependencies.clear();

	SImageDependency dep;
	while (rdata.getStr(&dep.volume)
		&& rdata.getVarInt(&dep.snapshot_id))
	{
		dependencies.push_back(dep);

		if (!snapshot_group_loginfo.empty())
		{
			snapshot_group_loginfo += ", ";
		}

		snapshot_group_loginfo += dep.volume;
	}

	ret_dependencies = dependencies;

	ServerLogger::Log(logid, "Image backup is being backed up in a snapshot group together with volumes " + snapshot_group_loginfo, LL_INFO);

	client_main->sendToPipe("WAKEUP");
	
	return true;
}
