/*************************************************************************
*    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 <http://www.gnu.org/licenses/>.
**************************************************************************/

#include "client.h"
#include "ParallelHash.h"
#include "../Interface/Server.h"
#include "../Interface/File.h"
#include "../Interface/SettingsReader.h"
#include "../Interface/Condition.h"
#ifdef _WIN32
#include "DirectoryWatcherThread.h"
#else
#include <errno.h>
#endif
#include "../stringtools.h"
#include "../common/data.h"
#include "../md5.h"
#include "database.h"
#include "ServerIdentityMgr.h"
#include "ClientService.h"
#include "../urbackupcommon/sha2/sha2.h"
#include <algorithm>
#include <fstream>
#include <stdlib.h>
#include "file_permissions.h"
#include <memory>
#include <assert.h>
#include "../urbackupcommon/chunk_hasher.h"
#include "../urbackupcommon/TreeHash.h"
#include "../fileservplugin/chunk_settings.h"
#include "ImageThread.h"
#include "../common/adler32.h"
#include <string.h>

#ifdef __APPLE__
#include <iostream>
#include <sys/xattr.h>
#include <sys/types.h>
#endif

//For truncating files
#ifdef _WIN32
#include <io.h>
#include <fcntl.h>
#include <sys\stat.h>
#include "win_disk_mon.h"
#include "win_all_volumes.h"
#include "win_locked.h"
#else
#include "../config.h"
#ifdef HAVE_MNTENT_H
#include <mntent.h>
#endif
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#endif
#include <set>
#include "../urbackupcommon/glob.h"
#include "TokenCallback.h"

#ifndef _WIN32
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/file.h>
#endif

#if defined(__ANDROID__)
#include <sys/socket.h>
#include <sys/types.h>
#include "../urbackupcommon/android_popen.h"
#endif


volatile bool IdleCheckerThread::idle=false;
volatile bool IdleCheckerThread::pause=false;
volatile bool IndexThread::stop_index=false;
std::map<std::string, std::string> IndexThread::filesrv_share_dirs;
IMutex* IndexThread::cbt_shadow_id_mutex;
std::map<std::string, int> IndexThread::cbt_shadow_ids;
std::map<unsigned int, IndexThread::SResult> IndexThread::index_results;
unsigned int IndexThread::next_result_id = 1;
IMutex* IndexThread::result_mutex = nullptr;

const char IndexThread::IndexThreadAction_StartFullFileBackup=0;
const char IndexThread::IndexThreadAction_StartIncrFileBackup=1;
const char IndexThread::IndexThreadAction_CreateShadowcopy = 2;
const char IndexThread::IndexThreadAction_ReferenceShadowcopy = 11;
const char IndexThread::IndexThreadAction_ReleaseShadowcopy = 3;
const char IndexThread::IndexThreadAction_GetLog=9;
const char IndexThread::IndexThreadAction_PingShadowCopy=10;
const char IndexThread::IndexThreadAction_AddWatchdir = 5;
const char IndexThread::IndexThreadAction_RemoveWatchdir = 6;
const char IndexThread::IndexThreadAction_RestartFilesrv = 7;
const char IndexThread::IndexThreadAction_Stop = 8;
const char IndexThread::IndexThreadAction_SnapshotCbt = 12;
const char IndexThread::IndexThreadAction_WriteTokens = 13;
const char IndexThread::IndexThreadAction_UpdateCbt = 14;

extern PLUGIN_ID filesrv_pluginid;

#ifdef __APPLE__
    std::vector<std::string> IndexThread::macos_exclusions;
    std::vector<std::string> IndexThread::macos_overrides;
#endif

namespace
{
	const int64 idletime = 60000;
	const unsigned int nonidlesleeptime = 500;
	const unsigned short tcpport = 35621;
	const unsigned short udpport = 35622;
	const unsigned int shadowcopy_timeout = 7 * 24 * 60 * 60 * 1000;
	const unsigned int shadowcopy_startnew_timeout = 55 * 60 * 1000;
	const size_t max_file_buffer_size = 4 * 1024 * 1024;
	const int64 file_buffer_commit_interval = 120 * 1000;
	const int64 link_file_min_size = 2048;
}


void IdleCheckerThread::operator()(void)
{
	int lx,ly;
	int x,y;
	getMousePos(x,y);
	lx=x;
	ly=y;

	int64 last_move=Server->getTimeMS();

	while(true)
	{
		Server->wait(1000);
		getMousePos(x,y);
		if(x!=lx || y!=ly )
		{
			lx=x;
			ly=y;
			last_move=Server->getTimeMS();
			idle=false;
		}
		else if(Server->getTimeMS()-last_move>idletime)
		{
			idle=true;
		}
	}
}

bool IdleCheckerThread::getIdle(void)
{
	return true;//idle;
}

bool IdleCheckerThread::getPause(void)
{
	return pause;
}

void IdleCheckerThread::setPause(bool b)
{
	pause=b;
}

namespace
{
#ifdef _WIN32

	struct ON_DISK_USN_JOURNAL_DATA
	{
		uint64 MaximumSize; 
		uint64 AllocationDelta;
		uint64 UsnJournalID;
		int64 LowestValidUsn;
	};

	int64 getUsnNum( const std::string& dir, int64& sequence_id )
	{
		WCHAR volume_path[MAX_PATH]; 
		BOOL ok = GetVolumePathNameW(Server->ConvertToWchar(dir).c_str(), volume_path, MAX_PATH);
		if(!ok)
		{
			Server->Log("GetVolumePathName(dir, volume_path, MAX_PATH) failed in getUsnNum", LL_ERROR);
			return -1;
		}

		std::string vol=Server->ConvertFromWchar(volume_path);

		if(vol.size()>0)
		{
			if(vol[vol.size()-1]=='\\')
			{
				vol.erase(vol.size()-1,1);
			}
		}

		if(!vol.empty() && vol[0]!='\\')
		{
			vol = "\\\\.\\"+vol;
		}

		HANDLE hVolume=CreateFileW(Server->ConvertToWchar(vol).c_str(), GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
		if(hVolume==INVALID_HANDLE_VALUE)
		{
			Server->Log("CreateFile of volume '"+vol+"' failed. - getUsnNum", LL_ERROR);
			return -1;
		}

		USN_JOURNAL_DATA data;
		DWORD r_bytes;
		BOOL b=DeviceIoControl(hVolume, FSCTL_QUERY_USN_JOURNAL, NULL, 0, &data, sizeof(USN_JOURNAL_DATA), &r_bytes, NULL);

		CloseHandle(hVolume);

		if(b)
		{
			sequence_id=data.UsnJournalID;
			return data.NextUsn;
		}
		else
		{
			std::unique_ptr<IFile> journal_info(Server->openFile(vol+"\\$Extend\\$UsnJrnl:$Max", MODE_READ_SEQUENTIAL_BACKUP));

			if(journal_info.get()==NULL) return -1;

			ON_DISK_USN_JOURNAL_DATA journal_data = {};
			if(journal_info->Read(reinterpret_cast<char*>(&journal_data), sizeof(journal_data))!=sizeof(journal_data))
			{
				return -1;
			}

			sequence_id = journal_data.UsnJournalID;

			std::unique_ptr<IFile> journal(Server->openFile(vol+"\\$Extend\\$UsnJrnl:$J", MODE_READ_SEQUENTIAL_BACKUP));

			if(journal.get()==NULL) return -1;

			return journal->Size();
		}
	}

	HANDLE cbtMutex = NULL;
	int cbtMutexLocked = 0;

	void init_cbt_mutex()
	{
		cbtMutex = CreateMutexW(NULL, FALSE, L"6aaec69c-8d78-4c44-abcf-659207dc20ed");
	}

	bool lock_cbt_mutex()
	{
		assert(cbtMutex != NULL);
		if (cbtMutex == NULL)
		{
			return false;
		}
		if (cbtMutexLocked>0)
		{
			++cbtMutexLocked;
			return true;
		}
		if (WaitForSingleObject(cbtMutex, INFINITE) == WAIT_OBJECT_0)
		{
			++cbtMutexLocked;
			return true;
		}
		assert(false);
		return false;
	}

	void unlock_cbt_mutex()
	{
		assert(cbtMutex != NULL);
		if (cbtMutex == NULL)
		{
			return;
		}
		--cbtMutexLocked;
		if (cbtMutexLocked == 0)
		{
			ReleaseMutex(cbtMutex);
		}
		assert(cbtMutexLocked>=0);
	}

	struct ScopedUnlockCbtMutex
	{
		~ScopedUnlockCbtMutex() {
			unlock_cbt_mutex();
		}
	};

	std::vector<std::string> get_cbt_paths()
	{
		HKEY urbackup_cbt_key;
		if (RegCreateKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\UrBackupCbt",
			0, NULL, 0, KEY_ALL_ACCESS, NULL, &urbackup_cbt_key, NULL) == ERROR_SUCCESS)
		{
			WCHAR szBuffer[8192];
			DWORD dwBufferSize = sizeof(szBuffer)*sizeof(WCHAR);
			ULONG nError;
			DWORD dwType = REG_MULTI_SZ;
			nError = RegQueryValueExW(urbackup_cbt_key, L"cbt_paths", 0, &dwType,
				(LPBYTE)szBuffer, &dwBufferSize);
			RegCloseKey(urbackup_cbt_key);
			if (ERROR_SUCCESS == nError
				&& dwType==REG_MULTI_SZ)
			{
				std::wstring rval(szBuffer, szBuffer + dwBufferSize/sizeof(wchar_t));
				std::string strValue = Server->ConvertFromWchar(rval);
				std::vector<std::string> toks;
				std::string sep;
				sep.resize(1);
				sep[0] = 0;
				Tokenize(strValue, toks, sep);
				std::vector<std::string> ret;
				for (size_t i = 0; i < toks.size(); ++i)
				{
					std::string b = trim(toks[i]);
					if (!b.empty())
					{
						ret.push_back(b);
					}
				}
				return ret;
			}
		}
		return std::vector<std::string>();
	}

	bool add_cbt_path(const std::string& path)
	{
		lock_cbt_mutex();
		ScopedUnlockCbtMutex unlock_cbt_mutex;
		std::vector<std::string> curr_paths = get_cbt_paths();

		if (std::find(curr_paths.begin(), curr_paths.end(),
			path) == curr_paths.end())
		{
			curr_paths.push_back(path);
		}
		else
		{
			return true;
		}

		std::wstring data;

		for (size_t i = 0; i < curr_paths.size(); ++i)
		{
			data.append(Server->ConvertToWchar(curr_paths[i]));
			data.append(1, (wchar_t)0);
		}

		HKEY urbackup_cbt_key;
		if (RegCreateKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\UrBackupCbt",
			0, NULL, 0, KEY_ALL_ACCESS, NULL, &urbackup_cbt_key, NULL) == ERROR_SUCCESS)
		{
			LSTATUS status = RegSetValueExW(urbackup_cbt_key, L"cbt_paths", 0,
				REG_MULTI_SZ, reinterpret_cast<const BYTE*>(data.c_str()), static_cast<DWORD>((data.size() + 1)*sizeof(wchar_t)));
			RegCloseKey(urbackup_cbt_key);
			return status
				== ERROR_SUCCESS;
		}
		return false;
	}
#endif

#ifndef _WIN32
	std::string getFolderMount(const std::string& path)
	{		
#ifndef HAVE_MNTENT_H
		return std::string();
#else
		FILE *aFile;

		aFile = setmntent("/proc/mounts", "r");
		if (aFile == NULL) {
			return std::string();
		}
		struct mntent *ent;
		std::string maxmount;
		while (NULL != (ent = getmntent(aFile)))
		{
			if(path.find(ent->mnt_dir)==0 &&
				std::string(ent->mnt_dir).size()>maxmount.size())
			{
				maxmount = ent->mnt_dir;
			}
		}
		endmntent(aFile);

		return maxmount;
#endif //HAVE_MNTENT_H
	}
#endif //!_WIN32

#ifndef _WIN32
	std::string getMountDevice(const std::string& path)
	{		
#ifndef HAVE_MNTENT_H
		return std::string();
#else
		if(path.find("/dev/")==0)
		{
			return path;
		}

		FILE *aFile;

		aFile = setmntent("/proc/mounts", "r");
		if (aFile == NULL) {
			return std::string();
		}
		struct mntent *ent;
		while (NULL != (ent = getmntent(aFile)))
		{
			if(std::string(ent->mnt_dir)==path)
			{
				endmntent(aFile);
				return ent->mnt_fsname;
			}
		}
		endmntent(aFile);

		return std::string();
#endif //HAVE_MNTENT_H
	}
#else //!_WIN32
	std::string getMountDevice(const std::string& path)
	{
		return std::string();
	}
#endif

#ifndef _WIN32
	std::string getDeviceMount(const std::string& dev)
	{		
#ifndef HAVE_MNTENT_H
		return std::string();
#else
		FILE *aFile;

		aFile = setmntent("/proc/mounts", "r");
		if (aFile == NULL) {
			return std::string();
		}
		struct mntent *ent;
		while (NULL != (ent = getmntent(aFile)))
		{
			if(std::string(ent->mnt_fsname)==dev)
			{
				endmntent(aFile);
				return ent->mnt_dir;
			}
		}
		endmntent(aFile);

		return std::string();
#endif //HAVE_MNTENT_H
	}
#endif //!_WIN32


#ifndef _WIN32
	std::vector<std::string> getAllMounts(const std::vector<std::string>& excl_fs, bool filter_usb)
	{
#ifndef HAVE_MNTENT_H
		return std::vector<std::string>();
#else
		std::vector<std::string> usb_devs;

		if (filter_usb)
		{
			std::vector<SFile> lnks = getFiles("/dev/disk/by-id");

			for (size_t i = 0; i < lnks.size(); ++i)
			{
				if (lnks[i].issym
					&& next(lnks[i].name, 0, "usb-"))
				{
					std::string symtarget;
					if (os_get_symlink_target("/dev/disk/by-id/" + lnks[i].name, symtarget))
					{
						usb_devs.push_back(symtarget);
					}
				}
			}

			std::sort(usb_devs.begin(), usb_devs.end());
		}

		FILE *aFile;

		aFile = setmntent("/proc/mounts", "r");
		if (aFile == NULL) {
			Server->Log("Opening /proc/mounts failed. " + os_last_error_str(), LL_WARNING);
			return std::vector<std::string>();
		}
		struct mntent *ent;
		std::vector<std::string> ret;
		while (NULL != (ent = getmntent(aFile)))
		{
			if (!std::binary_search(excl_fs.begin(), excl_fs.end(), std::string(ent->mnt_type))
				&& !std::binary_search(usb_devs.begin(), usb_devs.end(), std::string(ent->mnt_fsname)) )
			{
				ret.push_back(ent->mnt_dir);
			}
		}
		endmntent(aFile);

		return ret;
#endif
	}
#endif
}

IMutex *IndexThread::filelist_mutex=nullptr;
IPipe* IndexThread::msgpipe=nullptr;
IFileServ *IndexThread::filesrv=nullptr;
IMutex *IndexThread::filesrv_mutex=nullptr;

std::string add_trailing_slash(const std::string &strDirName)
{
	if(!strDirName.empty() && strDirName[strDirName.size()-1]!=os_file_sep()[0])
	{
		return strDirName+os_file_sep();
	}
	else if(strDirName.empty())
	{
		return os_file_sep();
	}
	else
	{
		return strDirName;
	}
}

IndexThread::IndexThread(void)
	: index_error(false), last_filebackup_filetime(0), index_group(-1),
	with_scripts(false), volumes_cache(nullptr), phash_queue(nullptr),
	index_backup_dirs_optional(false), sc_refs_cleanup(false)
{
	if(filelist_mutex==nullptr)
		filelist_mutex=Server->createMutex();
	if(msgpipe==nullptr)
		msgpipe=Server->createMemoryPipe();
	if(filesrv_mutex==nullptr)
		filesrv_mutex=Server->createMutex();

	read_error_mutex = Server->createMutex();

	if (cbt_shadow_id_mutex == nullptr)
		cbt_shadow_id_mutex = Server->createMutex();

	if (result_mutex == nullptr)
		result_mutex = Server->createMutex();

	curr_result_id = 0;

	dwt=nullptr;

	if(Server->getPlugin(Server->getThreadID(), filesrv_pluginid))
	{
		start_filesrv();
	}
	else
	{
		filesrv=nullptr;
		Server->Log("Error starting fileserver", LL_ERROR);
	}

	modify_file_buffer_size=0;
	end_to_end_file_backup_verification=false;
	add_file_buffer_size=0;
	calculate_filehashes_on_client=false;
	last_tmp_update_time=0;
	last_file_buffer_commit_time=0;
}

IndexThread::~IndexThread()
{
	filesrv->stopServer();

#ifdef _WIN32
	if(dwt!=NULL)
	{
		dwt->stop();
		Server->getThreadPool()->waitFor(dwt_ticket);
		delete dwt;
	}
#endif

	((IFileServFactory*)(Server->getPlugin(Server->getThreadID(), filesrv_pluginid)))->destroyFileServ(filesrv);
	Server->destroy(filelist_mutex);
	Server->destroy(msgpipe);
	Server->destroy(filesrv_mutex);
	Server->destroy(cbt_shadow_id_mutex);
	Server->destroy(read_error_mutex);
	Server->destroy(result_mutex);
	cd->destroyQueries();
	delete cd;
}

IMutex* IndexThread::getFilelistMutex(void)
{
	return filelist_mutex;
}

void IndexThread::updateDirs(void)
{
	readBackupDirs();
	readSnapshotGroups();

#ifdef _WIN32
	std::vector<std::string> watching;
	std::vector<ContinuousWatchEnqueue::SWatchItem> continuous_watch;
	for(size_t i=0;i<backup_dirs.size();++i)
	{
		if (backup_dirs[i].facet != index_facet_id)
			continue;

		watching.push_back(backup_dirs[i].path);

		if(backup_dirs[i].group==c_group_continuous)
		{
			continuous_watch.push_back(
				ContinuousWatchEnqueue::SWatchItem(backup_dirs[i].path, backup_dirs[i].tname));
		}
	}

	if(dwt==NULL)
	{
		dwt=new DirectoryWatcherThread(watching, continuous_watch);
		dwt_ticket=Server->getThreadPool()->execute(dwt, "directory watcher");
	}
	else
	{
		for(size_t i=0;i<backup_dirs.size();++i)
		{
			if (backup_dirs[i].facet != index_facet_id)
				continue;

			std::string msg="A"+backup_dirs[i].path;
			dwt->getPipe()->Write(msg);
		}
	}
#endif
}

void IndexThread::log_read_errors(const std::string& share_name, const std::string& orig_path)
{
	IScopedLock lock2(read_error_mutex);

	for (size_t i = 0; i < read_errors.size();)
	{
		if (read_errors[i].sharename == share_name)
		{
			VSSLog("There was a read error during the last file backup while backing up the file \"" + read_errors[i].filepath + "\" at position " + convert(read_errors[i].filepos)
				+ " in backup path \""+ orig_path +"\" ("+read_errors[i].msg+"). "
				+ "This might have prevented the backup from finishing. "
				"If this keeps occuring, please have a look at the system error log and at the disk S.M.A.R.T. values.", LL_WARNING);

			read_errors.erase(read_errors.begin() + i);
		}
		else
		{
			++i;
		}
	}
}

void IndexThread::operator()(void)
{
	Server->waitForStartupComplete();

#ifdef _WIN32
	init_win_locked();
	initVss();
	init_cbt_mutex();
	if (os_get_file_type("urbctctl.exe") != 0)
	{
		add_cbt_path(Server->getServerWorkingDir() + os_file_sep() + "urbackup");
	}
#endif

	if(backgroundBackupsEnabled(std::string()))
	{
#ifndef _DEBUG
		background_prio.reset(new ScopedBackgroundPrio());
#endif
	}
	

	db=Server->getDatabase(Server->getThreadID(), URBACKUPDB_CLIENT);
	cd=new ClientDAO(Server->getDatabase(Server->getThreadID(), URBACKUPDB_CLIENT));

	cleanup_saved_shadowcopies();
	
	updateDirs();
	register_token_callback();

	updateCbt();

	std::string last_index;
	bool async_timeout = false;
	int64 async_timeout_starttime = 0;

	while(true)
	{
		if (curr_result_id != 0)
		{
			setResultFinished(curr_result_id);
		}

		std::string msg;
		msgpipe->Read(&msg, sc_refs_cleanup ? 60000 : -1);

		if (msg.empty())
		{
			run_sc_refs_cleanup();
			continue;
		}

		async_timeout = false;
		CRData data(&msg);
		char action;
		data.getChar(&action);
		data.getUInt(&curr_result_id);
		if(action==IndexThreadAction_StartIncrFileBackup
			|| ( (last_index=="full" || last_index=="vfull") && action == IndexThreadAction_StartFullFileBackup ) )
		{
			Server->Log("Removing VSS log data...", LL_DEBUG);
			vsslog.clear();

			if (action == IndexThreadAction_StartFullFileBackup)
			{
				if (last_index == "full")
				{
					VSSLog("Last full index unfinished. Performing incremental (virtual full) indexing...", LL_INFO);
				}
				else
				{
					VSSLog("Last virtual full index unfinished. Performing incremental (virtual full) indexing...", LL_INFO);
				}
			}

			data.getStr(&starttoken);
			data.getInt(&index_group);
			unsigned int flags;
			data.getUInt(&flags);
			data.getStr(&index_clientsubname);
			data.getInt(&sha_version);
			int running_jobs = 2;
			data.getInt(&running_jobs);
			data.getInt(&index_facet_id);
			char async_index = 0;
			data.getChar(&async_index);
			std::string async_ticket;
			data.getStr2(&async_ticket);

			if (async_index == 1)
			{
				async_timeout = true;
			}

			if (!async_ticket.empty())
			{
				initParallelHashing(async_ticket);
				addResult(curr_result_id, "phash");
			}

			setFlags(flags);

			//incr backup
			bool has_dirs = readBackupDirs();
			bool has_scripts = readBackupScripts(action == IndexThreadAction_StartFullFileBackup);
			bool has_vss_components = getVssSettings();
			if( !has_dirs && !has_scripts && !has_vss_components)
			{
				addResult(curr_result_id, "no backup dirs");

				if (async_timeout)
				{
					async_timeout_starttime = Server->getTimeMS();
				}

				continue;
			}
#ifdef _WIN32
			if(cd->hasChangedGap())
			{
				Server->Log("Deleting file-index... GAP found...", LL_INFO);

				std::vector<std::string> gaps=cd->getGapDirs();

				std::string q_str="DELETE FROM files WHERE (tgroup=0 OR tgroup=?)";
				if(!gaps.empty())
				{
					q_str+=" AND (";
				}
				for(size_t i=0;i<gaps.size();++i)
				{
					q_str+="name GLOB ?";
					if(i+1<gaps.size())
						q_str+=" OR ";
				}
				if (!gaps.empty())
				{
					q_str += ")";
				}

				IQuery *q=db->Prepare(q_str, false);
				q->Bind(index_group+1);
				for(size_t i=0;i<gaps.size();++i)
				{
					Server->Log("Deleting file-index from drive \""+gaps[i]+"\"", LL_INFO);
					q->Bind(gaps[i]+"*");
				}

				q->Write();
				q->Reset();
				db->destroyQuery(q);

				if(dwt!=NULL)
				{
					dwt->stop();
					Server->getThreadPool()->waitFor(dwt_ticket);
					delete dwt;
					dwt=NULL;
					updateDirs();
				}
			}
#endif
			monitor_disk_failures();

			int e_rc;
			if ( (e_rc=execute_prebackup_hook(true, starttoken, index_group))!=0 )
			{
				addResult(curr_result_id, "error - prefilebackup script failed with error code "+convert(e_rc));
			}
			else if(!stop_index)
			{
				if (action == IndexThreadAction_StartIncrFileBackup)
				{
					last_index = "incr";
				}
				else
				{
					last_index = "vfull";
				}

				IndexErrorInfo error_info = indexDirs(false, running_jobs>1);

				if ( (e_rc=execute_postindex_hook(true, starttoken, index_group, error_info))!=0 )
				{
					addResult(curr_result_id, "error - postfileindex script failed with error code " + convert(e_rc));
				}
				else if(stop_index)
				{
					addResult(curr_result_id, "error - stopped indexing 2");
				}
				else if(index_error)
				{
					if(error_info==IndexErrorInfo_NoBackupPaths)
						addResult(curr_result_id, "no backup dirs");
					else
						addResult(curr_result_id, "error - index error");
				}
				else
				{
					addResult(curr_result_id, "done");
				}
			}
			else
			{
				addResult(curr_result_id, "error - stop_index 1");
			}

			if (async_timeout)
			{
				async_timeout_starttime = Server->getTimeMS();
			}

			if (phash_queue != nullptr
				&& phash_queue->deref())
			{
				delete phash_queue;
			}
			phash_queue = nullptr;
		}
		else if(action==IndexThreadAction_StartFullFileBackup)
		{
			Server->Log("Removing VSS log data...", LL_DEBUG);
			vsslog.clear();

			data.getStr(&starttoken);
			data.getInt(&index_group);
			unsigned int flags;
			data.getUInt(&flags);
			data.getStr(&index_clientsubname);
			data.getInt(&sha_version);
			int running_jobs = 2;
			data.getInt(&running_jobs);
			data.getInt(&index_facet_id);
			char async_index = 0;
			data.getChar(&async_index);
			std::string async_ticket;
			data.getStr2(&async_ticket);

			if (async_index == 1)
			{
				async_timeout = true;
			}

			if (!async_ticket.empty())
			{
				initParallelHashing(async_ticket);
				addResult(curr_result_id, "phash");
			}

			setFlags(flags);

			bool has_dirs = readBackupDirs();
			bool has_scripts = readBackupScripts(true);
			bool has_vss_components = getVssSettings();
			if(!has_dirs && !has_scripts && !has_vss_components)
			{
				addResult(curr_result_id, "no backup dirs");

				if (async_timeout)
				{
					async_timeout_starttime = Server->getTimeMS();
				}

				continue;
			}
			//full backup
			{
				Server->Log("Deleting files... doing full index...", LL_INFO);
				resetFileEntries();
			}

			monitor_disk_failures();

			int e_rc;
			if ( (e_rc=execute_prebackup_hook(false, starttoken, index_group))!=0 )
			{
				addResult(curr_result_id, "error - prefilebackup script failed with error code "+convert(e_rc));
			}
			else
			{
				last_index = "full";

				IndexErrorInfo error_info = indexDirs(true, running_jobs>1);

				if ( (e_rc=execute_postindex_hook(false, starttoken, index_group, error_info))!=0 )
				{
					addResult(curr_result_id, "error - postfileindex script failed with error code "+convert(e_rc));
				}
				else if (stop_index)
				{
					addResult(curr_result_id, "error - stopped indexing");
				}
				else if (index_error)
				{
					if(error_info==IndexErrorInfo_NoBackupPaths)
						addResult(curr_result_id, "no backup dirs");
					else
						addResult(curr_result_id, "error - index error");
				}
				else
				{
					addResult(curr_result_id, "done");
				}
			}

			if (async_timeout)
			{
				async_timeout_starttime = Server->getTimeMS();
			}

			if (phash_queue != nullptr
				&& phash_queue->deref())
			{
				delete phash_queue;
			}
			phash_queue = nullptr;
		}
		else if(action==IndexThreadAction_CreateShadowcopy
			 || action==IndexThreadAction_ReferenceShadowcopy )
		{
			vsslog.clear();
			if (action == IndexThreadAction_CreateShadowcopy)
			{
				readSnapshotGroups();
			}

			std::string scdir;
			data.getStr(&scdir);
			data.getStr(&starttoken);
			uchar image_backup=0;
			data.getUChar(&image_backup);
			unsigned char fileserv;
			bool hfs=data.getUChar(&fileserv);

			int running_jobs = 2;
			index_clientsubname.clear();
			if (hfs)
			{
				data.getStr(&index_clientsubname);
				data.getInt(&running_jobs);
			}

#ifdef _WIN32
			if(action==IndexThreadAction_ReferenceShadowcopy
				&& image_backup==0
				&& (scdir == "windows_components" || scdir == "windows_components_config") )
			{
				addResult(curr_result_id, "done--");
				continue;
			}
#endif

			if (image_backup != 0)
			{
				int rc = execute_preimagebackup_hook(image_backup == 2, starttoken);

				if (rc != 0)
				{
					VSSLog("Pre image backup hook failed with error code " + convert(rc), LL_ERROR);
					addResult(curr_result_id, "failed");
					continue;
				}
			}

			bool reference_sc = action == IndexThreadAction_ReferenceShadowcopy;

			SCDirs *scd = getSCDir(scdir, index_clientsubname, image_backup!=0);
			
			if(scd->running==true && Server->getTimeSeconds()-scd->starttime<shadowcopy_timeout/1000)
			{
				if(scd->ref!=nullptr && image_backup==0)
				{
					scd->ref->dontincrement=true;
				}
				bool onlyref = reference_sc;
				if(start_shadowcopy(scd, &onlyref, image_backup!=0?true:false, running_jobs>1, std::vector<SCRef*>(), image_backup!=0?true:false))
				{
					if (scd->ref!=nullptr
						&& !onlyref)
					{
						for (size_t k = 0; k < sc_refs.size(); ++k)
						{
							if (sc_refs[k]->ssetid == scd->ref->ssetid)
							{
								if (sc_refs[k]->cbt)
								{
									sc_refs[k]->cbt = finishCbt(sc_refs[k]->target,
										image_backup != 0 ? sc_refs[k]->save_id : -1, sc_refs[k]->volpath,
										image_backup != 0, sc_refs[k]->cbt_file, sc_refs[k]->cbt_type);
								}
							}
						}
					}

					if (scd->ref != nullptr
						&& !scd->ref->cbt
						&& !disableCbt(scd->orig_target) )
					{
						VSSLog("Error disabling change block tracking for " + scd->orig_target, LL_ERROR);
						addResult(curr_result_id, "failed");
					}
					else
					{
						addResult(curr_result_id, "done-" + convert(scd->ref->save_id) + "-" + scd->target + otherVolumeInfo(scd, onlyref));
					}
				}
				else
				{
					if (!disableCbt(scd->orig_target))
					{
						VSSLog("Error disabling change block tracking for " + scd->orig_target+" (2)", LL_ERROR);
					}
					VSSLog("Getting shadowcopy of \""+scd->dir+"\" failed.", LL_ERROR);
					addResult(curr_result_id, "failed");
				}
			}
			else
			{
				if(scd->running==true)
				{
					Server->Log("Removing shadowcopy \""+scd->dir+"\" because of timeout...", LL_WARNING);
					bool b=release_shadowcopy(scd, false, -1, scd);
					if(!b)
					{
						Server->Log("Deleting shadowcopy of \""+scd->dir+"\" failed.", LL_ERROR);
					}
				}

				scd->dir=scdir;
				scd->starttime=Server->getTimeSeconds();
				if(hfs && fileserv==0)
				{
					scd->target=scd->dir;
					scd->fileserv=false;
				}
				else
				{
					scd->target=getShareDir(scd->dir);
					scd->fileserv=true;
				}

				scd->orig_target=scd->target;

				Server->Log("Creating shadowcopy of \""+scd->dir+"\"...", LL_DEBUG);
				bool onlyref = reference_sc;
				bool b= start_shadowcopy(scd, &onlyref, image_backup!=0?true:false, running_jobs>1, std::vector<SCRef*>(), image_backup==0?false:true);
				Server->Log("done.", LL_DEBUG);
				if(!b || scd->ref==nullptr)
				{
					if(scd->fileserv)
					{
						shareDir(std::string(), scd->dir, scd->target);
					}

					if (!disableCbt(scd->orig_target))
					{
						VSSLog("Error disabling change block tracking for " + scd->orig_target + " (3)", LL_ERROR);
					}

					addResult(curr_result_id, "failed");
					Server->Log("Creating shadowcopy of \""+scd->dir+"\" failed.", LL_ERROR);
				}
				else
				{
					if (scd->ref != nullptr
						&& !onlyref)
					{
						for (size_t k = 0; k < sc_refs.size(); ++k)
						{
							if (sc_refs[k]->ssetid == scd->ref->ssetid)
							{
								if (sc_refs[k]->cbt)
								{
									sc_refs[k]->cbt = finishCbt(sc_refs[k]->target, 
										image_backup != 0 ? sc_refs[k]->save_id : -1, sc_refs[k]->volpath,
										image_backup != 0, sc_refs[k]->cbt_file, sc_refs[k]->cbt_type);
								}
							}
						}
					}

					if (scd->ref != nullptr
						&& !scd->ref->cbt
						&& !disableCbt(scd->orig_target))
					{
						VSSLog("Error disabling change block tracking for " + scd->orig_target, LL_ERROR);

						if (scd->fileserv)
						{
							shareDir(starttoken, scd->dir, scd->target);
						}

						addResult(curr_result_id, "failed");
					}
					else
					{
						addResult(curr_result_id, "done-" + convert(scd->ref->save_id) + "-" + scd->target + otherVolumeInfo(scd, onlyref));
						scd->running = true;
					}

					if ((image_backup != 0 && FileExists("create_md5sums_imagebackup"))
						|| (image_backup == 0 && FileExists("create_md5sums_filebackup")))
					{
						createMd5sumsFile(removeDirectorySeparatorAtEnd(scd->target), scd->orig_target);
					}
				}
			}
		}
		else if(action==IndexThreadAction_ReleaseShadowcopy) // remove shadowcopy
		{
			vsslog.clear();

			std::string scdir;
			data.getStr(&scdir);
			data.getStr(&starttoken);
			uchar image_backup=0;
			data.getUChar(&image_backup);

			int save_id=-1;
			data.getInt(&save_id);
			index_clientsubname.clear();
			data.getStr(&index_clientsubname);
			int issues = 0;
			data.getInt(&issues);

#ifdef _WIN32
			if (image_backup == 0
				&& (scdir == "windows_components" || scdir == "windows_components_config"))
			{
				addResult(curr_result_id, "done");
				continue;
			}
#endif		

			int64 starttime = Server->getTimeMS();

			while (filesrv != nullptr
				&& filesrv->hasActiveTransfers(scdir, starttoken)
				&& Server->getTimeMS() - starttime < 5000)
			{
				Server->wait(100);
			}

			if (filesrv != nullptr
				&& filesrv->hasActiveTransfers(scdir, starttoken) )
			{
				addResult(curr_result_id, "in use");
			}
			else
			{
				bool del_error = false;
#ifdef _WIN32
				std::map<std::string, SVssInstance*>::iterator it_inst = vss_name_instances.find(starttoken + "|" + scdir);
				if (it_inst != vss_name_instances.end())
				{
					--it_inst->second->refcount;
					it_inst->second->issues += issues;

					for (size_t i = 0; i < it_inst->second->parents.size(); ++i)
					{
						it_inst->second->parents[i]->issues += issues;
					}

					if (it_inst->second->refcount == 0)
					{
						if(it_inst->second->set_succeeded)
						{
							HRESULT hr = it_inst->second->backupcom->SetBackupSucceeded(it_inst->second->instanceId,
								it_inst->second->writerId, it_inst->second->componentType,
								it_inst->second->logicalPath.empty() ? NULL : Server->ConvertToWchar(it_inst->second->logicalPath).c_str(),
								Server->ConvertToWchar(it_inst->second->componentName).c_str(),
								it_inst->second->issues == 0 ? true : false);

							if (hr != S_OK)
							{
								VSSLog("Error setting component \"" + it_inst->second->componentName + "\" with logical path \"" + it_inst->second->logicalPath +
									"\" to succeeded. VSS error code " + GetErrorHResErrStr(hr), LL_ERROR);
								del_error = true;
							}
						}

						for (size_t i = 0; i < it_inst->second->parents.size(); ++i)
						{
							SVssInstance* parent = it_inst->second->parents[i];

							assert(parent->refcount > 0);
							--parent->refcount;

							if (parent->refcount == 0)
							{
								if(parent->set_succeeded)
								{
									HRESULT hr = parent->backupcom->SetBackupSucceeded(parent->instanceId,
										parent->writerId, parent->componentType,
										parent->logicalPath.empty() ? NULL : Server->ConvertToWchar(parent->logicalPath).c_str(),
										Server->ConvertToWchar(parent->componentName).c_str(),
										parent->issues == 0 ? true : false);

									if (hr != S_OK)
									{
										VSSLog("Error setting component \"" + parent->componentName + "\" with logical path \"" + parent->logicalPath +
											"\" to succeeded. VSS error code " + GetErrorHResErrStr(hr), LL_ERROR);
										del_error = true;
									}
								}

								delete parent;
							}
						}

						delete it_inst->second;
					}

					vss_name_instances.erase(it_inst);
				}
#endif

				SCDirs *scd = getSCDir(scdir, index_clientsubname, image_backup!=0);
				if (scd->running == false)
				{
					if (!release_shadowcopy(scd, image_backup != 0 ? true : false, save_id))
					{
						Server->Log("Invalid action -- Creating shadow copy failed?", LL_ERROR);
						addResult(curr_result_id, "failed");
					}
					else
					{
						addResult(curr_result_id, del_error ? "failed" : "done");
					}
				}
				else
				{
					std::string release_dir = scd->dir;
					bool b = release_shadowcopy(scd, image_backup != 0 ? true : false, save_id);
					if (!b)
					{
						addResult(curr_result_id, "failed");
						Server->Log("Deleting shadowcopy of \"" + release_dir + "\" failed.", LL_ERROR);
					}
					else
					{
						addResult(curr_result_id, del_error ? "failed" : "done");
					}
				}
			}
		}
		else if(action==4) //lookup shadowdrive path
		{
			int save_id;
			if(data.getInt(&save_id))
			{
				std::string path=lookup_shadowcopy(save_id);
				if(path.empty())
				{
					addResult(curr_result_id, "failed");
				}
				else
				{
					for (size_t i = 0; i < sc_refs.size(); ++i)
					{
						if (sc_refs[i]->save_id == save_id)
						{
							sc_refs[i]->starttime = Server->getTimeSeconds();
						}
					}

					cd->updateShadowCopyStarttime(save_id);

					addResult(curr_result_id, "done-"+convert(save_id)+"-"+path);
				}
			}
		}
#ifdef _WIN32
		else if(action==IndexThreadAction_AddWatchdir)
		{
			std::string dir;
			if(data.getStr(&dir))
			{
				std::string msg="A"+os_get_final_path(dir);
				dwt->getPipe()->Write(msg);
			}
			std::string name;
			if(data.getStr(&name))
			{
				std::string msg="C"+os_get_final_path(dir)+"|"+(name);
				dwt->getPipe()->Write(msg);
			}
			addResult(curr_result_id, "done");
			stop_index=false;
		}
		else if(action==IndexThreadAction_RemoveWatchdir)
		{
			std::string dir;
			if(data.getStr(&dir))
			{
				std::string msg="D"+os_get_final_path(dir);
				dwt->getPipe()->Write(msg);
			}
			std::string name;
			if(data.getStr(&name))
			{
				std::string msg="X"+os_get_final_path(dir)+"|"+(name);
				dwt->getPipe()->Write(msg);
			}
			addResult(curr_result_id, "done");
			stop_index=false;
		}
#endif
		else if(action== IndexThreadAction_RestartFilesrv) // restart filesrv
		{
			IScopedLock lock(filesrv_mutex);
			filesrv->stopServer();
			start_filesrv();
			readBackupDirs();
		}
		else if(action==IndexThreadAction_Stop) //stop
		{
			break;
		}
		else if(action==IndexThreadAction_GetLog)
		{
			if (last_index == "full")
			{
				last_index = "full_done";
			}
			else if (last_index == "incr")
			{
				last_index = "incr_done";
			}
			else if (last_index == "vfull")
			{
				last_index = "vfull_done";
			}

			std::string ret;
			ret+="0-"+convert(Server->getTimeSeconds())+"-\n";
			for(size_t i=0;i<vsslog.size();++i)
			{
				ret+=convert(vsslog[i].loglevel)+"-"+convert(vsslog[i].times)+"-"+vsslog[i].msg+"\n";
			}
			Server->Log("VSS logdata - "+convert(ret.size())+" bytes", LL_DEBUG);
			addResult(curr_result_id, ret);
		}
		else if(action==IndexThreadAction_PingShadowCopy)
		{
			std::string scdir;
			data.getStr(&scdir);
			int save_id=-1;
			data.getInt(&save_id);
			index_clientsubname.clear();
			data.getStr(&index_clientsubname);

			SCDirs *scd = getSCDir(scdir, index_clientsubname, true);

			if(scd!=nullptr && scd->ref!=nullptr)
			{
				scd->ref->starttime = Server->getTimeSeconds();
			}
			if (scd != nullptr)
			{
				scd->starttime = Server->getTimeSeconds();
			}

			if(save_id!=-1)
			{
				cd->updateShadowCopyStarttime(save_id);
			}
		}
		else if (action == IndexThreadAction_UpdateCbt)
		{
			updateCbt();
		}
		else if (action == IndexThreadAction_SnapshotCbt)
		{
			std::string volume;
			data.getStr(&volume);

			if (prepareCbt(volume)
				&& finishCbt(volume, -1, std::string(), false, std::string(), CbtType_None))
			{
				addResult(curr_result_id, "done");
			}
			else
			{
				addResult(curr_result_id, "failed");
			}
		}
		else if (action == IndexThreadAction_WriteTokens)
		{
			data.getStr(&starttoken);
			async_timeout = true;
			writeTokens();
			addResult(curr_result_id, "done");
			async_timeout_starttime = Server->getTimeMS();
		}
	}

	delete this;
}

IndexThread::IndexErrorInfo IndexThread::indexDirs(bool full_backup, bool simultaneous_other)
{
	readPatterns(index_facet_id, index_group, index_clientsubname,
		index_exclude_dirs, index_include_dirs,
		index_backup_dirs_optional);
	file_id = 0;

	updateDirs();

	writeTokens();

	token_cache.reset();

#ifdef _WIN32
	bool backup_with_vss_components = index_group==0 
		&& (vss_select_all_components || !vss_select_components.empty());
#else
	bool backup_with_vss_components = false;
#endif

	index_follow_last = false;
	index_keep_files = false;

	updateBackupDirsWithAll();

	std::vector<std::string> selected_dirs;
	std::vector<int> selected_dir_db_tgroup;
	for(size_t i=0;i<backup_dirs.size();++i)
	{
		if (backup_dirs[i].facet != index_facet_id)
			continue;

		if(backup_dirs[i].group==index_group
			|| (backup_with_vss_components
				&& backup_dirs[i].group== c_group_vss_components) )
		{
			if (isAllSpecialDir(backup_dirs[i]))
				continue;

			selected_dirs.push_back(removeDirectorySeparatorAtEnd(backup_dirs[i].path));
#ifdef _WIN32
			selected_dirs[selected_dirs.size()-1]=strlower(selected_dirs[selected_dirs.size()-1]);
#endif
			if (backup_dirs[i].flags & EBackupDirFlag_ShareHashes)
			{
				selected_dir_db_tgroup.push_back(0);
			}
			else
			{
				selected_dir_db_tgroup.push_back(index_group + 1);
			}

			if (backup_dirs[i].flags & EBackupDirFlag_KeepFiles
				&& !full_backup
				&& !backup_dirs[i].reset_keep)
			{
				index_follow_last = true;
			}
		}
	}

#ifdef _WIN32
	//Invalidate cache
	DirectoryWatcherThread::freeze();
	DirectoryWatcherThread::update_and_wait(open_files);

	changed_dirs.clear();
	for(size_t i=0;i<selected_dirs.size();++i)
	{
		std::vector<std::string> acd=cd->getChangedDirs(selected_dirs[i], true);
		changed_dirs.insert(changed_dirs.end(), acd.begin(), acd.end() );
		DirectoryWatcherThread::reset_mdirs(selected_dirs[i]);
	}

	//move GAP dirs to backup table
	cd->getChangedDirs("##-GAP-##", true);
	DirectoryWatcherThread::reset_mdirs("##-GAP-##");
	
	for(size_t i=0;i<selected_dirs.size();++i)
	{
		std::vector<std::string> deldirs=cd->getDelDirs(selected_dirs[i]);
		VSSLog("Removing deleted directories from index...", LL_DEBUG);
		for(size_t j=0;j<deldirs.size();++j)
		{
			cd->removeDeletedDir(deldirs[j], selected_dir_db_tgroup[i]);
		}
	}

	std::string tmp = cd->getMiscValue("last_filebackup_filetime_lower");
	if(!tmp.empty())
	{
		last_filebackup_filetime = watoi64(tmp);
	}
	else
	{
		last_filebackup_filetime = 0;
	}

	_i64 last_filebackup_filetime_new = DirectoryWatcherThread::get_current_filetime();
#endif

	bool has_stale_shadowcopy=false;
	bool has_active_transaction = false;

	std::sort(changed_dirs.begin(), changed_dirs.end());


	for(size_t i=0;i<backup_dirs.size();++i)
	{
		if (backup_dirs[i].facet != index_facet_id)
			continue;

		backup_dirs[i].symlinked_confirmed=false;
	}

	std::vector<SCRef*> past_refs;

	last_tmp_update_time=Server->getTimeMS();
	index_error=false;

	std::string filelist_dest_fn = "urbackup/data_"+convert(index_facet_id)+"/filelist.ub";
	if (index_group != c_group_default)
	{
		filelist_dest_fn = "urbackup/data_"+convert(index_facet_id)+"/filelist_" + convert(index_group) + ".ub";
	}

	IFile* last_filelist_f = nullptr;
	ScopedFreeObjRef<IFile*> last_filelist_f_scope(last_filelist_f);
	if (index_follow_last)
	{
		last_filelist_f = Server->openFile(filelist_dest_fn, MODE_READ);

		if (last_filelist_f == nullptr)
		{
			index_follow_last = false;
		}

		last_filelist.reset(new SLastFileList);
		last_filelist->f = last_filelist_f;
	}

	std::string filelist_fn = "urbackup/data_"+convert(index_facet_id)+
		"/filelist_new_" + convert(index_group) + ".ub";

	std::streamoff outfile_size = 0;
	{
		std::fstream outfile(filelist_fn.c_str(), std::ios::out|std::ios::binary);

#ifdef _WIN32
		if (index_group == 0)
		{
			if (backup_with_vss_components)
			{
				index_flags = EBackupDirFlag_FollowSymlinks | EBackupDirFlag_SymlinksOptional | EBackupDirFlag_ShareHashes;
				index_follow_last = false;
				VSS_ID ssetid;
				if (!start_shadowcopy_components(ssetid, &has_active_transaction))
				{
					index_error = true;
					VSSLog("Indexing Windows components failed", LL_ERROR);
					return IndexErrorInfo_Error;
				}
				else
				{
					addScRefs(ssetid, past_refs);

					SCDirs* empty = NULL;
					postSnapshotProcessing(empty, full_backup);

					for (size_t k = 0; k < sc_refs.size(); ++k)
					{
						if (sc_refs[k]->ssetid == ssetid)
						{
							if (sc_refs[k]->cbt)
							{
								sc_refs[k]->cbt = finishCbt(sc_refs[k]->target, -1, sc_refs[k]->volpath,
									false, sc_refs[k]->cbt_file, sc_refs[k]->cbt_type);
							}
							postSnapshotProcessing(sc_refs[k], full_backup);
						}
					}

					if (!vss_all_components.empty())
					{
						bool orig_with_sequence = with_sequence;
						with_sequence = false;
						indexVssComponents(ssetid, !full_backup, past_refs, outfile);
						with_sequence = orig_with_sequence;

						if (cd->getMiscValue("has_components") != "1")
						{
							cd->updateMiscValue("has_components", "1");
						}
					}
				}
			}
		}

		removeUnconfirmedVssDirs();
#endif

		for(size_t i=0;i<backup_dirs.size();++i)
		{
			if (backup_dirs[i].facet != index_facet_id)
				continue;

			if(backup_dirs[i].group!=index_group)
			{
				continue;
			}
			
			if(backup_dirs[i].symlinked &&
			    !backup_dirs[i].symlinked_confirmed)
			{
				continue;
			}

			if (isAllSpecialDir(backup_dirs[i]))
				continue;

			if (isExcluded(index_exclude_dirs, backup_dirs[i].path)
				&& isExcluded(index_exclude_dirs, backup_dirs[i].tname))
			{
				continue;
			}

			index_server_default = backup_dirs[i].server_default;

			SCDirs *scd=getSCDir(backup_dirs[i].tname, index_clientsubname, false);
			if(!scd->running)
			{
				scd->dir=backup_dirs[i].tname;
				scd->starttime=Server->getTimeSeconds();
				scd->target=getShareDir(backup_dirs[i].tname);
				scd->orig_target=scd->target;
			}
			scd->fileserv=true;

			std::string mod_path=backup_dirs[i].path;

#ifdef _WIN32
			if(mod_path.size()==2) //e.g. C:
			{
				mod_path+=os_file_sep();
			}
#endif
			int filetype = os_get_file_type(os_file_prefix(mod_path));

			bool shadowcopy_optional = (backup_dirs[i].flags & EBackupDirFlag_Optional)
				|| (backup_dirs[i].symlinked && (backup_dirs[i].flags & EBackupDirFlag_SymlinksOptional) );

			if (!shadowcopy_optional && !(backup_dirs[i].flags & EBackupDirFlag_Required)
				&& !backup_dirs[i].symlinked && index_backup_dirs_optional)
			{
				shadowcopy_optional = true;
			}

			bool onlyref=false;
			bool stale_shadowcopy=false;
			bool shadowcopy_ok=false;
			bool shadowcopy_not_configured = false;

			if(filetype!=0 || !shadowcopy_optional)
			{
				VSSLog("Creating shadowcopy of \""+scd->dir+"\" in indexDirs()", LL_DEBUG);	
				shadowcopy_ok=start_shadowcopy(scd, &onlyref, true, simultaneous_other, past_refs,
					false, &stale_shadowcopy, &shadowcopy_not_configured, &has_active_transaction);
				VSSLog("done.", LL_DEBUG);
			}
			else if(shadowcopy_optional)
			{
				onlyref = true;
				std::string err_msg;
				int64 errcode = os_last_error(err_msg);

				VSSLog("Cannot access \""+mod_path+"\". Not creating snapshot. Errorcode: "+convert(errcode)+" - "+trim(err_msg), LL_DEBUG);
			}

			if(stale_shadowcopy)
			{
				has_stale_shadowcopy=true;
			}

			if(!shadowcopy_ok)
			{
				if(!shadowcopy_optional && !shadowcopy_not_configured)
				{
					VSSLog("Creating snapshot of \""+scd->dir+"\" failed.", LL_ERROR);
				}
				else
				{
					VSSLog("Backing up \"" + scd->dir + "\" without snapshot.", LL_INFO);
				}

				if (backup_dirs[i].flags & EBackupDirFlag_RequireSnapshot)
				{
					index_error = true;
				}
				else
				{
					shareDir(starttoken, backup_dirs[i].tname, removeDirectorySeparatorAtEnd(backup_dirs[i].path));
				}

				if (!disableCbt(backup_dirs[i].path))
				{
					VSSLog("Error disabling change block tracking", LL_ERROR);
					index_error = true;
				}
			}
			else
			{
				mod_path=scd->target;
				scd->running=true;
			}

			if (!index_error && !stop_index)
			{
				mod_path = removeDirectorySeparatorAtEnd(mod_path);
				backup_dirs[i].path = removeDirectorySeparatorAtEnd(backup_dirs[i].path);

#ifdef _WIN32
				if (mod_path.size() == 2) //e.g. C:
				{
					mod_path += os_file_sep();
				}
#endif
				std::string extra;

				std::string volume = backup_dirs[i].path;
				normalizeVolume(volume);

#ifdef _WIN32
				if (!shadowcopy_ok || !onlyref)
				{
					if (scd->ref != NULL)
					{
						addScRefs(scd->ref->ssetid, past_refs);
					}
					
					postSnapshotProcessing(scd, full_backup);
				}
#else
				if (!onlyref)
				{
					past_refs.push_back(scd->ref);

					postSnapshotProcessing(scd, full_backup);
				}
#endif
				if (scd->ref != nullptr
					&& !scd->ref->cbt)
				{
					if (!disableCbt(backup_dirs[i].path))
					{
						VSSLog("Error disabling change block tracking of \"" + backup_dirs[i].path + "\"...", LL_ERROR);
						index_error = true;
					}
				}

				for (size_t k = 0; k < changed_dirs.size(); ++k)
				{
					VSSLog("Changed dir: " + changed_dirs[k], LL_DEBUG);
				}

				if (!index_error)
				{
					VSSLog("Indexing \"" + backup_dirs[i].tname + "\"...", LL_DEBUG);
				}
				index_c_db = 0;
				index_c_fs = 0;
				index_c_db_update = 0;
				//db->BeginWriteTransaction();
				last_transaction_start = Server->getTimeMS();
				index_root_path = mod_path;
				index_keep_files = (backup_dirs[i].flags & EBackupDirFlag_KeepFiles) > 0
					&& !backup_dirs[i].reset_keep;

				std::string vssvolume = mod_path;
				normalizeVolume(vssvolume);

#ifndef _WIN32
				if (index_root_path.empty())
				{
					index_root_path = os_file_sep();
				}
#endif
				if (!index_error)
				{
					openCbtHdatFile(scd->ref, backup_dirs[i].tname, volume);

					std::vector<std::string> rm_exclude_dirs = getRmExcludedByPatterns(index_exclude_dirs, backup_dirs[i].path);

					for (size_t k = 0; k < rm_exclude_dirs.size(); ++k)
					{
						VSSLog("\"" + backup_dirs[i].tname + "\" at \"" + backup_dirs[i].path +
							"\" would be completely excluded by pattern \"" + rm_exclude_dirs[k] +
							"\". Not using this pattern while indexing this path", LL_DEBUG);
					}
					
					std::vector<SRecurParams> params_stack;
					initialCheck(params_stack, std::string::npos,
						strlower(volume), vssvolume, backup_dirs[i].path, mod_path, backup_dirs[i].tname, outfile, true,
						backup_dirs[i].flags, !full_backup, backup_dirs[i].symlinked, 0, true, true,
						index_exclude_dirs, index_include_dirs, std::string());

					index_exclude_dirs.insert(index_exclude_dirs.end(), rm_exclude_dirs.begin(), rm_exclude_dirs.end());
				}

				commitModifyFilesBuffer();
				commitAddFilesBuffer();
				commitModifyHardLinks();
				commitPhashQueue();
			}

			if(stop_index || index_error)
			{
				for(size_t k=0;k<backup_dirs.size();++k)
				{
					if (backup_dirs[k].facet != index_facet_id)
						continue;

					SCDirs *scd=getSCDir(backup_dirs[k].tname, index_clientsubname, false);
					release_shadowcopy(scd);
				}
				
				outfile.close();
				removeFile((filelist_fn));

				if(index_error)
				{
					VSSLog("Indexing files failed, because of error", LL_ERROR);
				}
				else
				{
					VSSLog("Indexing interrupted by configuration change", LL_ERROR);
				}

				return IndexErrorInfo_Error;
			}

			if(!backup_dirs[i].symlinked)
			{
				VSSLog("Indexing of \""+backup_dirs[i].tname+"\" done. "+convert(index_c_fs)+" filesystem lookups "+convert(index_c_db)+" db lookups and "+convert(index_c_db_update)+" db updates" , LL_INFO);
			}

			//Remove unreferenced symlinks now
			removeUnconfirmedSymlinkDirs(i+1);
		}

		if (outfile.is_open())
		{
			addBackupScripts(outfile);
		}

		std::streampos pos=outfile.tellp();
		outfile.seekg(0, std::ios::end);
		if(pos!=outfile.tellg())
		{
			outfile.close();
			bool b=os_file_truncate((filelist_fn), pos);
			if(!b)
			{
				VSSLog("Error changing filelist size", LL_ERROR);
			}

			outfile.open(filelist_fn.c_str(), std::ios::in|std::ios::out|std::ios::binary);
			if(outfile.is_open())
			{
				outfile.seekp(0, std::ios::end);
			}
			else
			{
				VSSLog("Error reopening filelist", LL_ERROR);
			}
		}

		outfile_size = static_cast<std::streamoff>(outfile.tellp());
	}

	commitModifyFilesBuffer();
	commitAddFilesBuffer();
	commitModifyHardLinks();

	if (phash_queue != nullptr)
	{
		CWData data;
		data.addChar(ID_PHASH_FINISH);
		addToPhashQueue(data);
	}

	commitPhashQueue();

	index_hdat_file.reset();

#ifdef _WIN32
	if(!has_stale_shadowcopy
		&& !has_active_transaction)
	{
		if(!index_error)
		{
			VSSLog("Deleting backup of changed dirs...", LL_DEBUG);
			cd->deleteSavedChangedDirs();
			cd->deleteSavedDelDirs();

			if(index_group==c_group_default)
			{
				DirectoryWatcherThread::update_last_backup_time();
				DirectoryWatcherThread::commit_last_backup_time();

				cd->updateMiscValue("last_filebackup_filetime_lower", convert(last_filebackup_filetime_new));
			}
		}
		else
		{
			VSSLog("Did not delete backup of changed dirs because there was an error while indexing which might not occur the next time.", LL_INFO);
		}
	}
	else
	{
		if (has_stale_shadowcopy)
		{
			VSSLog("Did not delete backup of changed dirs because a stale shadowcopy was used.", LL_INFO);
		}
		if (has_active_transaction)
		{
			VSSLog("Did not delete backup of changed dirs because at least one volume had an active NTFS transaction.", LL_INFO);
		}
	}

	DirectoryWatcherThread::unfreeze();
	open_files.clear();
	changed_dirs.clear();
	
#endif

	IndexErrorInfo ret = IndexErrorInfo_Ok;

	if (outfile_size == 0)
	{
		index_error = true;
		ret = IndexErrorInfo_NoBackupPaths;
	}

	if (last_filelist_f!=nullptr)
	{
		Server->destroy(last_filelist_f);
		last_filelist_f = nullptr;
		last_filelist.reset();
	}

	{
		IScopedLock lock(filelist_mutex);
		if(index_group==c_group_default)
		{
			if (FileExists(filelist_dest_fn)
				&& !removeFile(filelist_dest_fn))
			{
				VSSLog("Error deleting file "+ filelist_dest_fn+". "+os_last_error_str(), LL_ERROR);
			}
			if (!moveFile(filelist_fn, filelist_dest_fn))
			{
				VSSLog("Error renaming "+ filelist_fn+" to "+ filelist_dest_fn+". " + os_last_error_str(), LL_ERROR);
				index_error = true;
			}
		}
		else
		{
			if (FileExists(filelist_dest_fn)
				&& !removeFile(filelist_dest_fn))
			{
				VSSLog("Error deleting file "+ filelist_dest_fn+". " + os_last_error_str(), LL_ERROR);
			}
			if (!moveFile(filelist_fn, filelist_dest_fn))
			{
				VSSLog("Error renaming "+ filelist_fn+" to "+ filelist_dest_fn+". " + os_last_error_str(), LL_ERROR);
				index_error = true;
			}
		}
	}

	for (size_t i = 0; i < backup_dirs.size(); ++i)
	{
		if (backup_dirs[i].facet != index_facet_id)
			continue;

		if (backup_dirs[i].group == index_group
			&& backup_dirs[i].reset_keep)
		{
			cd->setResetKeep(0, backup_dirs[i].id);
		}
	}

	share_dirs();

	for (size_t i = 0; i < backup_dirs.size(); ++i)
	{
		if (backup_dirs[i].facet != index_facet_id)
			continue;

		if (backup_dirs[i].group == index_group
			&& (!backup_dirs[i].symlinked || backup_dirs[i].symlinked_confirmed) )
		{
			log_read_errors(starttoken + "|" + backup_dirs[i].tname, backup_dirs[i].path);
			log_read_errors(backup_dirs[i].tname, backup_dirs[i].path);
		}
	}

	changed_dirs.clear();

	if (ret == IndexErrorInfo_Ok
		&& index_error)
	{
		ret = IndexErrorInfo_Error;
	}

	return ret;
}

void IndexThread::updateBackupDirsWithAll()
{
	bool has_all = false;
	bool has_all_nonusb = false;
	size_t ref_idx;
	std::vector<std::string> filter_add;
	for (size_t i = 0; i < backup_dirs.size(); ++i)
	{
		if (backup_dirs[i].facet != index_facet_id)
			continue;

		if (backup_dirs[i].group == index_group
			&& backup_dirs[i].path == "ALL" && backup_dirs[i].tname == "ALL"
			&& backup_dirs[i].server_default == EBackupDirServerDefault_Default)
		{
			ref_idx = i;
			has_all = true;
			filter_add.push_back("tmpfs");
		}
		else if (backup_dirs[i].group == index_group
			&& backup_dirs[i].path == "ALL_NONUSB" &&  backup_dirs[i].tname == "ALL_NONUSB"
			&& backup_dirs[i].server_default == EBackupDirServerDefault_Default)
		{
			ref_idx = i;
			has_all_nonusb = true;
			filter_add.push_back("tmpfs");
		}
#ifndef _WIN32
		else if (backup_dirs[i].group == index_group
			&& backup_dirs[i].path == "ALL_NONET" &&  backup_dirs[i].tname == "ALL_NONET"
			&& backup_dirs[i].server_default == EBackupDirServerDefault_Default)
		{
			ref_idx = i;
			has_all = true;
			filter_add.push_back("nfs");
			filter_add.push_back("smbfs");
			filter_add.push_back("cifs");
		}
		else if (backup_dirs[i].group == index_group
			&& backup_dirs[i].path == "ALL_WITH_TMPFS" &&  backup_dirs[i].tname == "ALL_WITH_TMPFS"
			&& backup_dirs[i].server_default == EBackupDirServerDefault_Default)
		{
			ref_idx = i;
			has_all = true;
		}
#endif
	}

	std::vector<std::string> volumes;
#ifdef _WIN32
	if (has_all)
	{
		std::string vols = get_all_volumes_list(false, volumes_cache);
		Tokenize(vols, volumes, ";,");
	}
	else if (has_all_nonusb)
	{
		std::string vols = get_all_volumes_list(true, volumes_cache);
		Tokenize(vols, volumes, ";,");
	}
#else
	if (has_all || has_all_nonusb)
	{
#ifndef HAVE_MNTENT_H
		VSSLog("Error getting mounted file systems. Client not compiled with HAVE_MNTENT_H", LL_ERROR);
#endif
		filter_add.push_back("sysfs");
		filter_add.push_back("proc");
		filter_add.push_back("devtmpfs");
		filter_add.push_back("devpts");
		filter_add.push_back("securityfs");
		filter_add.push_back("cgroup");
		filter_add.push_back("pstore");
		filter_add.push_back("autofs");
		filter_add.push_back("debugfs");
		filter_add.push_back("mqueue");
		filter_add.push_back("hugetlbfs");
		filter_add.push_back("configfs");
		filter_add.push_back("cgroup2");
		filter_add.push_back("fusectl");
		filter_add.push_back("efivarfs");
		std::sort(filter_add.begin(), filter_add.end());
		volumes = getAllMounts(filter_add, has_all_nonusb);
	}
#endif

	bool new_volumes = false;

	std::vector<std::string> volumes_norm;
	if (!volumes.empty())
	{
		IQuery *q_add = db->Prepare("INSERT INTO backupdirs (name, path, server_default, optional, tgroup, facet) VALUES (?, ? ," + convert((int)EBackupDirServerDefault_AllSrc) + ", ?, ?, ?)", false);
		IQuery *q_name = db->Prepare("SELECT id FROM backupdirs WHERE name=?", false);

		for (size_t i = 0; i < volumes.size(); ++i)
		{
			std::string cvol = trim(volumes[i]);
#ifdef _WIN32
			if (!normalizeVolume(cvol))
				continue;
#endif

			if (cvol[cvol.size() - 1] == '\\' || cvol[cvol.size() - 1] == '/')
			{
				cvol.erase(cvol.size() - 1, 1);
#ifndef _WIN32
				if (cvol.empty())
				{
					cvol = "/";
				}
#endif
			}

			volumes_norm.push_back(cvol);

			bool found_watch = false;
			bool found = false;
			for (size_t j = 0; j < backup_dirs.size(); ++j)
			{
				if (backup_dirs[j].facet != index_facet_id)
					continue;

				std::string ovol = backup_dirs[j].path;
#ifdef _WIN32
				if (ovol.size() <= 3)
					normalizeVolume(ovol);
#endif

				if (ovol == cvol)
				{
					found_watch = true;
				}
				if (ovol == cvol
					&& backup_dirs[j].group == index_group)
				{
					found = true;
				}
			}

#ifdef _WIN32
			if (!found_watch)
			{
				std::string msg = "A" + os_get_final_path(cvol);
				dwt->getPipe()->Write(msg);
			}
#endif

			if (!found)
			{
				new_volumes = true;

				std::string name = volumes[i];

#ifndef _WIN32
				if (name.empty() || name == "/")
					name = "rootfs";
				else
					name = ExtractFileName(name);
#endif

				q_name->Bind(name);
				std::string orig_name = name;
				if (q_name->Read().empty() == false
					|| name == "urbackup")
				{
					for (int k = 0; k < 100; ++k)
					{
						q_name->Reset();
						q_name->Bind(name + "_" + convert(k));
						if (q_name->Read().empty() == true)
						{
							name += "_" + convert(k);
							break;
						}
					}

					if (orig_name == name)
					{
						name += "_" + Server->secureRandomString(16);
					}
				}
				q_name->Reset();

				int new_flags = backup_dirs[ref_idx].flags;

#ifndef _WIN32
				new_flags |= EBackupDirFlag_OneFilesystem;
#endif

				q_add->Bind(ClientConnector::removeIllegalCharsFromBackupName(name));
				q_add->Bind(cvol);
				q_add->Bind(new_flags);
				q_add->Bind(index_group);
				q_add->Bind(index_facet_id);
				q_add->Write();
				q_add->Reset();
			}
		}

		db->destroyQuery(q_add);
		db->destroyQuery(q_name);
	}

	IQuery* q_del = nullptr;
	for (size_t i = 0; i < backup_dirs.size();)
	{
		if (backup_dirs[i].facet != index_facet_id)
		{
			++i;
			continue;
		}

		if (backup_dirs[i].group == index_group
			&& backup_dirs[i].server_default == EBackupDirServerDefault_AllSrc)
		{
			std::string ovol = backup_dirs[i].path;
#ifdef _WIN32
			if (ovol.size() <= 3)
				normalizeVolume(ovol);
#endif

			if (std::find(volumes_norm.begin(), volumes_norm.end(), ovol)== volumes_norm.end())
			{
				if(q_del==nullptr)
					q_del = db->Prepare("DELETE FROM backupdirs WHERE id=?", false);

				std::string cpath = backup_dirs[i].path;
				q_del->Bind(backup_dirs[i].id);
				q_del->Write();
				q_del->Reset();
				backup_dirs.erase(backup_dirs.begin() + i);

#ifdef _WIN32
				bool found = false;
				for (size_t j = 0; j < backup_dirs.size(); ++j)
				{
					if (backup_dirs[j].facet != index_facet_id)
						continue;

					if (backup_dirs[j].path == cpath)
					{
						found = true;
						break;
					}
				}

				if (!found)
				{
					std::string msg = "D" + os_get_final_path(cpath);
					dwt->getPipe()->Write(msg);
				}
#endif

				continue;
			}				
		}
		++i;
	}
	db->destroyQuery(q_del);

	if (new_volumes)
	{
		CWData data;
		data.addChar(IndexThread::IndexThreadAction_UpdateCbt);
		IndexThread::getMsgPipe()->Write(data.getDataPtr(), data.getDataSize());

		readBackupDirs();
	}
}

void IndexThread::resetFileEntries(void)
{
	db->Write("DELETE FROM files WHERE tgroup=0 OR tgroup="+convert(index_group+1));
	cd->deleteSavedChangedDirs();
	cd->resetAllHardlinks();
#ifdef _WIN32
	DirectoryWatcherThread::reset_mdirs(std::string());
#endif
}

bool IndexThread::skipFile(const std::string& filepath, const std::string& namedpath,
	const std::vector<std::string>& exclude_dirs,
	const std::vector<SIndexInclude>& include_dirs)
{
	if( isExcluded(exclude_dirs, filepath) || (!namedpath.empty() && isExcluded(exclude_dirs, namedpath) ) )
	{
		return true;
	}
	if( !isIncluded(include_dirs, filepath, nullptr)
		&& (namedpath.empty() || !isIncluded(include_dirs, namedpath, nullptr) ) )
	{
		return true;
	}

	return false;
}

bool IndexThread::initialCheck(std::vector<SRecurParams>& params_stack, size_t stack_idx, 
	const std::string& volume, const std::string& vssvolume, std::string orig_dir, std::string dir, std::string named_path, std::fstream &outfile,
	bool first, int flags, bool use_db, bool symlinked, size_t depth, bool dir_recurse, bool include_exclude_dirs, const std::vector<std::string>& exclude_dirs,
	const std::vector<SIndexInclude>& include_dirs, const std::string& orig_path)
{
	index_flags=flags;

	if( IdleCheckerThread::getIdle()==false )
	{
		Server->wait(nonidlesleeptime);
	}
	if(IdleCheckerThread::getPause())
	{
		Server->wait(5000);
	}

	if(stop_index)
	{
		return false;
	}

	SFirstInfo first_info;
	bool close_dir=false;
	std::string extra;
	
	if(first)
	{
		std::string curr_dir = os_file_prefix(dir);
		int filetype = os_get_file_type(curr_dir);

		if(filetype==0)
		{
			curr_dir = os_file_prefix(add_trailing_slash(dir));
			filetype = os_get_file_type(curr_dir);
		}

		if(!(filetype & EFileType_File) && !(filetype & EFileType_Directory))
		{
			if(!(flags & EBackupDirFlag_Optional) 
				&& (!symlinked || !(flags & EBackupDirFlag_SymlinksOptional) )
				&& ( (flags & EBackupDirFlag_Required)
					 || symlinked
					 || !index_backup_dirs_optional ) )
			{
				std::string err_msg;
				int64 errcode = os_last_error(err_msg);

				VSSLog("Cannot access path to backup: \""+dir+"\" Errorcode: "+convert(errcode)+" - "+trim(err_msg), LL_ERROR);
				index_error=true;

#ifdef _WIN32
				if (!getbetween("%", "%", orig_dir).empty())
				{
					VSSLog("Hint: The path to backup contains Windows environment variables. This is not supported. "
						"UrBackup Clients runs as user independent system service. See https://www.urbackup.org/faq.html#include_files for what you probably want to do.", LL_WARNING);
				}

				if (next(orig_dir, 0, "\\\\")
					&& errcode == ERROR_ACCESS_DENIED)
				{
					VSSLog("Hint: Access to network share denied. "
						"If you are trying to backup a network location please be aware "
						"that the UrBackup client runs as \"LOCAL SYSTEM\" user per default. "
						"See https://www.urbackup.org/faq.html#use_shares on how to backup network locations", LL_WARNING);
				}
#endif
				if (!os_directory_exists(orig_dir))
				{
					if (orig_dir.find("*") != std::string::npos || orig_dir.find("?") != std::string::npos)
					{
						VSSLog("Hint: The directory to backup contains wild cards. This is not supported. "
							"Please use the include and exclude settings to accomplish what you want", LL_WARNING);
					}

					VSSLog("Hint: Directory to backup (\"" + orig_dir + "\") does not exist. It may have been deleted or renamed. "
						"Set the \"optional\" directory flag if you do not want backups to fail if this directory is missing or "
						"set \"Directories to backup are optional by default\" in the file backup settings.", LL_WARNING);

#ifdef _WIN32
					if (orig_dir.size() > 1
						&& orig_dir[1] == ':')
					{
						std::string vol = add_trailing_slash(orig_dir).substr(0, 3);
						if (!os_directory_exists(vol))
						{
							VSSLog("Hint: Volume (\"" + vol + "\") does not exist. It may currently be not present. "
								"If you are trying to backup a network location via mapped network drives please be aware "
								"that mapped network drives are per user and the UrBackup client runs as \"LOCAL SYSTEM\" user per default. "
								"See https://www.urbackup.org/faq.html#use_shares on how to backup network locations", LL_WARNING);
						}
					}
#endif
				}
			}

			return false;
		}

		if(with_orig_path)
		{
			extra += "&orig_path=" + EscapeParamString(orig_path.empty() ? (orig_dir.empty() ? os_file_sep() : orig_dir) : orig_path)
				+ "&orig_sep=" + EscapeParamString(os_file_sep());
		}

#ifdef _WIN32
		int64 sequence_id;
		int64 sequence_next = getUsnNum(dir, sequence_id);
		if(sequence_next!=-1 && with_sequence)
		{
			extra += "&sequence_next="+convert(sequence_next) +
				"&sequence_id="+convert(sequence_id);
		}
#endif

		if(filetype & EFileType_File)
		{
			first_info.fn_filter = ExtractFileName(dir, os_file_sep());
			orig_dir = ExtractFilePath(orig_dir, os_file_sep());
			dir = ExtractFilePath(dir, os_file_sep());
		}
		else if(filetype & EFileType_Directory)
		{
			close_dir=true;

			SFile metadata = getFileMetadataWin(curr_dir, true);

			if(metadata.usn==0)
			{
				metadata.usn = metadata.last_modified;
			}
			
			addFromLastUpto(named_path, true, depth, false, outfile);
			writeDir(outfile, named_path, with_orig_path, metadata.usn, extra);
			extra.clear();

			++depth;
		}
	}

	int64 target_generation;
	std::vector<SFileAndHash> files=getFilesProxy(orig_dir, dir, named_path, !first && use_db, first_info.fn_filter, use_db,
		exclude_dirs, include_dirs, target_generation);

	if(index_error)
	{
		return false;
	}

	bool finish_phash_path = false;
	int64 phash_dir_id = file_id;
	int64 phash_dir_files = 0;
	bool has_include = false;
	
	for(size_t i=0;i<files.size();++i)
	{
		if( !files[i].isdir )
		{
			if( (files[i].issym && !with_proper_symlinks && !(flags & EBackupDirFlag_FollowSymlinks)) 
				|| (files[i].isspecialf && !with_proper_symlinks) )
			{
				continue;
			}

			if( skipFile(orig_dir+os_file_sep()+files[i].name, named_path+os_file_sep()+files[i].name,
				exclude_dirs, include_dirs) )
			{
				continue;
			}

			if (!use_db
				&& files[i].nlinks > 1)
			{
				enumerateHardLinks(volume, vssvolume, dir + os_file_sep() + files[i].name);
			}

			has_include=true;

			std::string listname = files[i].name;

			if(first && !first_info.fn_filter.empty() && i==0)
			{
				listname = named_path;
			}
			
			addFromLastUpto(listname, false, depth, false, outfile);

			outfile << "f\"" << escapeListName((listname)) << "\" " << files[i].size << " " << static_cast<int64>(files[i].change_indicator);
		
			if(calculate_filehashes_on_client
				&& !files[i].hash.empty() )
			{
				if(sha_version==256)
				{
					extra+="&sha256="+base64_encode_dash(files[i].hash);
				}
				else if (sha_version == 528)
				{
					extra += "&thash=" + base64_encode_dash(files[i].hash);
				}
				else
				{
					extra += "&sha512=" + base64_encode_dash(files[i].hash);
				}
			}
			else if (calculate_filehashes_on_client
				&& phash_queue != nullptr
				&& !files[i].isspecialf
				&& files[i].size>=link_file_min_size )
			{
				if (!finish_phash_path)
				{
					finish_phash_path = true;

					CWData wdata;
					wdata.addChar(ID_SET_CURR_DIRS);
					wdata.addVarInt(phash_dir_id);
					wdata.addString2(orig_dir);
					wdata.addInt(index_group);
					wdata.addString2(dir);
					addToPhashQueue(wdata);
				}

				++phash_dir_files;
				CWData wdata;
				wdata.addChar(ID_HASH_FILE);
				wdata.addVarInt(file_id);
				wdata.addString2(files[i].name);
				wdata.addVarInt(phash_dir_id);
				addToPhashQueue(wdata);
			}

			++file_id;
				
			if(end_to_end_file_backup_verification && !files[i].isspecialf)
			{
				extra+="&sha256_verify=" + getSHA256(dir+os_file_sep()+files[i].name);
			}

			if(files[i].issym && with_proper_symlinks)
			{
				extra+="&sym_target="+EscapeParamString((files[i].output_symlink_target));
			}
			
			if(files[i].isspecialf && with_proper_symlinks)
			{
				extra+="&special=1";
			}

			extra += "&line=" + convert(file_id);

			if(!extra.empty())
			{
				extra[0]='#';
				outfile << extra;
				extra.clear();
			}

			outfile << "\n";
		}
	}

	if (finish_phash_path)
	{
		CWData wdata;
		wdata.addChar(ID_FINISH_CURR_DIR);
		wdata.addVarInt(phash_dir_id);
		wdata.addVarInt(target_generation);
		wdata.addVarInt(phash_dir_files);
		addToPhashQueue(wdata);
	}

	if (first)
	{
		assert(params_stack.empty());
		SRecurParams curr_params(SFileAndHash(), nullptr, false,
			std::string(), std::string(), std::string(), depth, std::string::npos);
		curr_params.state = 2;
		params_stack.push_back(curr_params);
		stack_idx = params_stack.size() - 1;
	}

	for(size_t i=files.size();dir_recurse && i-->0;)
	{
		if( files[i].isdir )
		{
			if( (files[i].issym && !with_proper_symlinks && !(flags & EBackupDirFlag_FollowSymlinks) ) 
				|| (files[i].isspecialf && !with_proper_symlinks) )
			{
				continue;
			}

			/* Skip symlinks if they are not included?
			Problem is that we haven't scanned symlink contents yet and don't know if it has any included files...*/

			if (files[i].issym && with_proper_symlinks && !(flags & EBackupDirFlag_IncludeDirectorySymlinks)
				&& skipFile(orig_dir + os_file_sep() + files[i].name, named_path + os_file_sep() + files[i].name,
				exclude_dirs, include_dirs))
			{
				continue;
			}

			bool curr_included = false;
			bool adding_worthless1, adding_worthless2;

			if (include_exclude_dirs)
			{
				if (isExcluded(exclude_dirs, orig_dir + os_file_sep() + files[i].name)
					|| isExcluded(exclude_dirs, named_path + os_file_sep() + files[i].name))
				{
					continue;
				}
				
				if (isIncluded(include_dirs, orig_dir + os_file_sep() + files[i].name, &adding_worthless1)
					|| isIncluded(include_dirs, named_path + os_file_sep() + files[i].name, &adding_worthless2))
				{
					has_include = true;
					curr_included = true;
				}
			}
			else
			{
				curr_included = true;
				adding_worthless1 = false;
				adding_worthless2 = false;
			}

			if( curr_included ||  !adding_worthless1 || !adding_worthless2 )
			{
				first_info.idx = i;
				SRecurParams curr_params(files[i], first ? &first_info : nullptr, curr_included,
					orig_dir, dir, named_path, depth, stack_idx);
				params_stack.push_back(curr_params);
			}
		}
	}

	if (first)
	{
		while (!params_stack.empty())
		{
			SRecurParams& params_back = params_stack.back();
			size_t stack_idx = params_stack.size() - 1;

			if (params_back.state == 0)
			{
				params_back.state = 1;
				initialCheckRecur1(params_stack, params_back, stack_idx, volume, vssvolume, outfile, flags, use_db, dir_recurse,
					include_exclude_dirs, exclude_dirs, include_dirs, orig_path);

				if (index_error)
				{
					return has_include;
				}
			}
			else if(params_back.state == 1)
			{
				initialCheckRecur2(params_stack, params_back, stack_idx, volume, vssvolume, outfile, flags, use_db, dir_recurse,
					include_exclude_dirs, exclude_dirs, include_dirs, orig_path);
				params_stack.pop_back();
			}
			else if (params_back.state == 2)
			{
				if (params_back.recur_ret.has_include)
					has_include = true;

				params_stack.pop_back();
			}
			else
			{
				assert(false);
			}
		}
	}

	if(close_dir)
	{
		addFromLastLiftDepth(depth - 1, outfile);
		if(!with_proper_symlinks)
		{
			outfile << "d\"..\"\n";
		}
		else
		{
			outfile << "u\n";
		}

		++file_id;
	}

	return has_include;
}

void IndexThread::initialCheckRecur1(std::vector<SRecurParams>& params_stack, SRecurParams& params, const size_t stack_idx,
	const std::string & volume, const std::string & vssvolume,
	std::fstream & outfile, const int flags, const bool use_db, const bool dir_recurse, const bool include_exclude_dirs, 
	const std::vector<std::string>& exclude_dirs, const std::vector<SIndexInclude>& include_dirs, const std::string& orig_path)
{
	params.recur_ret.pos = outfile.tellp();
	params.recur_ret.file_id_backup = file_id;

	SLastFileList backup;
	if (index_follow_last)
	{
		backup = *last_filelist.get();
	}
	std::string extra;

	if (params.file.issym && with_proper_symlinks)
	{
		extra += "&sym_target=" + EscapeParamString((params.file.output_symlink_target));
	}

	if (params.file.isspecialf && with_proper_symlinks)
	{
		extra += "&special=1";
	}

	extra += "&line=" + convert(file_id + 1);

	std::string listname = params.file.name;

	if (params.first_info!=nullptr && !params.first_info->fn_filter.empty() && params.first_info->idx == 0)
	{
		listname = params.named_path;
	}

	addFromLastUpto(listname, true, params.depth, false, outfile);
	writeDir(outfile, listname, with_orig_path, params.file.change_indicator, extra);
	extra.clear();

	params.recur_ret.has_include = true;

	if (!params.file.issym || !with_proper_symlinks)
	{
		bool b_has_include = initialCheck(params_stack, stack_idx, 
			volume, vssvolume, params.orig_dir + os_file_sep() + params.file.name, params.dir + os_file_sep() + params.file.name,
			params.named_path + os_file_sep() + params.file.name, outfile, false, flags, use_db, false, params.depth + 1,
			dir_recurse, include_exclude_dirs, exclude_dirs, include_dirs, orig_path);
		params_stack[stack_idx].recur_ret.has_include = b_has_include;
	}
}

void IndexThread::initialCheckRecur2(std::vector<SRecurParams>& params_stack, SRecurParams& params, const size_t stack_idx,
	const std::string & volume, const std::string & vssvolume,
	std::fstream & outfile, const int flags, const bool use_db, const bool dir_recurse, const bool include_exclude_dirs,
	const std::vector<std::string>& exclude_dirs, const std::vector<SIndexInclude>& include_dirs, const std::string& orig_path)
{
	addFromLastLiftDepth(params.depth, outfile);

	if (!with_proper_symlinks)
	{
		outfile << "d\"..\"\n";
	}
	else
	{
		outfile << "u\n";
	}

	++file_id;

	if (!params.recur_ret.has_include)
	{
		if (!params.curr_included)
		{
			if (index_follow_last)
			{
				last_filelist->reset_to(params.recur_ret.backup);
			}

			outfile.seekp(params.recur_ret.pos);
			file_id = params.recur_ret.file_id_backup;
		}
	}
	else
	{
		if (params.parent_idx != std::string::npos)
		{
			params_stack.at(params.parent_idx).recur_ret.has_include = true;
		}
	}
}

void IndexThread::addResult(unsigned int id, const std::string & res)
{
	if (id == 0)
		return;

	IScopedLock lock(result_mutex);

	std::map<unsigned int, SResult>::iterator it = index_results.find(id);

	if (it == index_results.end())
		return;

	it->second.results.push_back(res);
	it->second.cond->notify_all();
}

void IndexThread::setResultFinished(unsigned int id)
{
	if (id == 0)
		return;

	IScopedLock lock(result_mutex);

	std::map<unsigned int, SResult>::iterator it = index_results.find(id);

	if (it == index_results.end())
		return;

	if (it->second.refs == 0)
	{
		Server->destroy(it->second.cond);
		index_results.erase(it);
	}
	else
	{
		it->second.finished = true;
	}
}

bool IndexThread::readBackupDirs(void)
{
	backup_dirs=cd->getBackupDirs();

	bool has_backup_dir = false;
	for(size_t i=0;i<backup_dirs.size();++i)
	{
		if (backup_dirs[i].facet != index_facet_id)
			continue;

		if (isAllSpecialDir(backup_dirs[i]))
		{
			if (index_group != -1 && backup_dirs[i].group == index_group)
			{
				has_backup_dir = true;
			}
			continue;
		}

		backup_dirs[i].path=os_get_final_path(backup_dirs[i].path);
		Server->Log("Final path: "+backup_dirs[i].path, LL_INFO);

		if(index_group!=-1 && backup_dirs[i].group == index_group)
		{
			has_backup_dir=true;
		}

		if(filesrv!=nullptr)
			shareDir("", backup_dirs[i].tname, backup_dirs[i].path);
	}
	return has_backup_dir;
}

bool IndexThread::readBackupScripts(bool full_backup)
{
	scripts.clear();

	if(!with_scripts)
	{
		return false;
	}
	
	std::string script_path = Server->getServerParameter("script_path");
	std::string script_cmd;

	if(script_path.empty())
	{
#ifdef _WIN32
		script_path = Server->getServerWorkingDir() + os_file_sep() + "backup_scripts";
#else
		script_path = "/etc/urbackup/scripts";
#endif
	}

	std::vector<std::string> script_paths;
#ifndef _WIN32
	Tokenize(script_path, script_paths, ":");
#else
	script_paths.push_back(script_path);
#endif

	std::string first_script_path;
	for (size_t j = 0; j < script_paths.size(); ++j)
	{
		std::string curr_script_path = script_paths[j];

		if (j == 0)
		{
			first_script_path = curr_script_path;
		}

		script_cmd = curr_script_path + os_file_sep() + "list";

		if (index_group != c_group_default)
		{
			script_cmd += "_" + conv_filename(index_clientsubname);
		}

#ifdef _WIN32
		script_cmd += ".bat";
#endif

		if (!FileExists(script_cmd))
		{
			Server->Log("Script list at \"" + script_cmd + "\" does not exist. Skipping.", LL_INFO);
			continue;
		}

		std::string output = execute_script(script_cmd, full_backup?"0":"1");

		std::vector<std::string> lines;
		Tokenize(output, lines, "\n");

		IScopedLock lock(filesrv_mutex);

		if (!lines.empty())
		{
			for (size_t i = 0; i < lines.size(); ++i)
			{
				std::string line = trim(lines[i]);
				if (!line.empty())
				{
					if (line[0] == '#')
					{
						continue;
					}

					SBackupScript new_script;

					str_map params;
					ParseParamStrHttp(line, &params);

					str_map::iterator it = params.find("scriptname");
					if (it == params.end())
					{
						continue;
					}

					new_script.scriptname = it->second;

					it = params.find("outputname");
					if (it != params.end())
					{
						new_script.outputname = it->second;
					}
					else
					{
						new_script.outputname = new_script.scriptname;
					}

					it = params.find("size");
					if (it != params.end())
					{
						new_script.size = watoi64(it->second);
					}
					else
					{
						new_script.size = -1;
					}

					bool tar_file = false;
					it = params.find("tar");
					if (it != params.end()
						&& it->second == "1")
					{
						new_script.size = 0;
						tar_file = true;
					}

					it = params.find("orig_path");
					if (it != params.end())
					{
						new_script.orig_path = it->second;
					}

					it = params.find("lastmod");
					if (it != params.end())
					{
						new_script.lastmod = watoi64(it->second);
					}
					else
					{
						new_script.lastmod = -1;
					}

					scripts.push_back(new_script);

					if (filesrv != nullptr)
					{
						filesrv->addScriptOutputFilenameMapping(new_script.outputname,
							new_script.scriptname, tar_file);

						if (j > 0)
						{
							filesrv->registerFnRedirect(first_script_path + os_file_sep() + new_script.outputname,
								curr_script_path + os_file_sep() + new_script.outputname);
						}
					}
				}
			}
		}
	}

	if (filesrv != nullptr && !scripts.empty() && !first_script_path.empty())
	{
		filesrv->shareDir("urbackup_backup_scripts", first_script_path, std::string(), true);
	}

	std::sort(scripts.begin(), scripts.end());
	
	return !scripts.empty();	
}

bool IndexThread::addMissingHashes(std::vector<SFileAndHash>* dbfiles, std::vector<SFileAndHash>* fsfiles, const std::string &orig_path,
	const std::string& filepath, const std::string& namedpath, const std::vector<std::string>& exclude_dirs,
	const std::vector<SIndexInclude>& include_dirs, bool calc_hashes)
{
	bool calculated_hash=false;

	if(fsfiles!=nullptr)
	{
		for(size_t i=0;i<fsfiles->size();++i)
		{
			SFileAndHash& fsfile = fsfiles->at(i);
			if( fsfile.isdir || fsfile.isspecialf)
				continue;

			if(!fsfile.hash.empty())
				continue;

			if (skipFile(orig_path + os_file_sep() + fsfile.name, namedpath + os_file_sep() + fsfile.name, exclude_dirs, include_dirs))
			{
				continue;
			}

			bool needs_hashing=true;

			if(dbfiles!=nullptr)
			{
				std::vector<SFileAndHash>::iterator it = std::lower_bound(dbfiles->begin(), dbfiles->end(), fsfile);

				if( it!=dbfiles->end()
					&& it->name==fsfile.name
					&& it->isdir==false
					&& it->change_indicator==fsfile.change_indicator
					&& it->size==fsfile.size
					&& !it->hash.empty() )
				{
					fsfile.hash=it->hash;
					needs_hashing=false;
				}
			}

			if(needs_hashing
				&& calc_hashes
				&& fsfile.size>= link_file_min_size)
			{
				fsfile.hash=getShaBinary(filepath+os_file_sep()+fsfile.name);
				calculated_hash=true;
			}
		}
	}
	else if(dbfiles!=nullptr
		&& calc_hashes)
	{
		for(size_t i=0;i<dbfiles->size();++i)
		{
			SFileAndHash& dbfile = dbfiles->at(i);
			if( dbfile.isdir || dbfile.isspecialf)
				continue;

			if(!dbfile.hash.empty())
				continue;

			if (skipFile(orig_path + os_file_sep() + dbfile.name, namedpath + os_file_sep() + dbfile.name, exclude_dirs, include_dirs))
			{
				continue;
			}

			if (dbfile.size < link_file_min_size)
				continue;

			dbfile.hash=getShaBinary(filepath+os_file_sep()+dbfile.name);
			calculated_hash=true;
		}
	}

	return calculated_hash;
}

bool IndexThread::hasHash(const std::vector<SFileAndHash>& fsfiles)
{
	for (size_t i = 0; i < fsfiles.size(); ++i)
	{
		if (!fsfiles[i].hash.empty())
			return true;
	}
	return false;
}

bool IndexThread::hasDirectory(const std::vector<SFileAndHash>& fsfiles)
{
	for (size_t i = 0; i < fsfiles.size(); ++i)
	{
		if (fsfiles[i].isdir)
			return true;
	}
	return false;
}

std::vector<SFileAndHash> IndexThread::getFilesProxy(const std::string &orig_path, std::string path, const std::string& named_path,
	bool use_db, const std::string& fn_filter, bool use_db_hashes, const std::vector<std::string>& exclude_dirs,
	const std::vector<SIndexInclude>& include_dirs, int64& target_generation)
{
	target_generation = 0;
#ifndef _WIN32
	if(path.empty())
	{
		path = os_file_sep();
	}
	std::string path_lower=orig_path + os_file_sep();
#else
	std::string path_lower=strlower(orig_path+os_file_sep());
#endif

	std::vector<std::string>::iterator it_dir=changed_dirs.end();
#ifdef _WIN32

	bool dir_changed=std::binary_search(changed_dirs.begin(), changed_dirs.end(), path_lower);
	
	if(path_lower==strlower(Server->getServerWorkingDir())+os_file_sep()+"urbackup"+os_file_sep())
	{
		use_db=false;
	}
#else
	use_db=false;
	bool dir_changed=true;
#endif
	std::vector<SFileAndHash> fs_files;
	if (!use_db || dir_changed)
	{
		++index_c_fs;

		std::string tpath = os_file_prefix(path);

		bool has_error;
		std::vector<SFile> os_files = getFilesWin(tpath, &has_error, true, true, (index_flags & EBackupDirFlag_OneFilesystem) > 0);
		filterEncryptedFiles(path, orig_path, os_files);
		fs_files = convertToFileAndHash(orig_path, named_path, exclude_dirs, include_dirs, os_files, fn_filter);

		if (has_error)
		{
#ifdef _WIN32
			DWORD err = GetLastError();
#else
			int err = errno;
#endif

			bool root_exists = os_directory_exists(os_file_prefix(index_root_path)) ||
				os_directory_exists(os_file_prefix(add_trailing_slash(index_root_path)));

			if (root_exists)
			{

#ifdef _WIN32
				SetLastError(err);
				VSSLog("Error while listing files in folder \"" + path + "\". "+os_last_error_str(), LL_ERROR);
#else
				VSSLog("Error while listing files in folder \"" + path + "\". User may not have permissions to access this folder. Errno is " + convert(err), LL_ERROR);
				index_error = true;
#endif
			}
			else
			{
#ifdef _WIN32
				SetLastError(err);
				VSSLog("Error while listing files in folder \"" + path + "\". Windows error: " + os_last_error_str() + ". Access to root directory is gone too. Shadow copy was probably deleted while indexing.", LL_ERROR);
#else
				VSSLog("Error while listing files in folder \"" + path + "\". Errorno is " + convert(err) + ". Access to root directory is gone too. Snapshot was probably deleted while indexing.", LL_ERROR);
#endif
				index_error = true;
			}
		}

		std::vector<SFileAndHash> db_files;
		bool has_files = false;

		if (use_db_hashes)
		{
#ifndef _WIN32
			if (calculate_filehashes_on_client)
			{
#endif
				has_files = cd->getFiles(path_lower, get_db_tgroup(), db_files, target_generation);
#ifndef _WIN32
			}
#endif
		}

#ifdef _WIN32
		if(dir_changed)
		{
			VSSLog("Indexing changed dir: " + path, LL_DEBUG);

			for(size_t i=0;i<fs_files.size();++i)
			{
				if(!fs_files[i].isdir)
				{
					if( std::binary_search(open_files.begin(), open_files.end(), path_lower+strlower(fs_files[i].name) ) )
					{
						VSSLog("File is open: " + fs_files[i].name, LL_DEBUG);

						if (fs_files[i].change_indicator == 0)
							fs_files[i].change_indicator += Server->getRandomNumber();

						fs_files[i].change_indicator *= (std::max)((unsigned int)2, Server->getRandomNumber());
						fs_files[i].change_indicator *= (std::max)((unsigned int)2, Server->getRandomNumber());

						if (fs_files[i].issym)
						{
							fs_files[i].change_indicator |= (change_indicator_symlink_bit | change_indicator_special_bit);
						}
						else if (fs_files[i].isspecialf)
						{
							fs_files[i].change_indicator |= change_indicator_special_bit;
						}
						else
						{
							fs_files[i].change_indicator &= ~change_indicator_all_bits;
						}
					}
				}
			}
		}
#endif


		if(calculate_filehashes_on_client
			&& (phash_queue==nullptr || has_files) )
		{
			addMissingHashes(has_files ? &db_files : nullptr, &fs_files, orig_path,
				path, named_path, exclude_dirs, include_dirs, phash_queue==nullptr);
		}

		if( has_files)
		{
			if(fs_files!=db_files)
			{
#ifndef _WIN32
				for (size_t i = 0; i < db_files.size(); ++i)
				{
					if (db_files[i].isdir
						&& !std::binary_search(fs_files.begin(), fs_files.end(), db_files[i]))
					{
						cd->removeDeletedDir(path_lower + db_files[i].name + os_file_sep(), get_db_tgroup());
					}
				}
#endif

				++index_c_db_update;
				modifyFilesInt(path_lower, get_db_tgroup(), fs_files, target_generation);
				++target_generation;
			}
		}
		else
		{
#ifndef _WIN32
			if(calculate_filehashes_on_client
				&& (hasHash(fs_files) || hasDirectory(fs_files) ) )
			{
#endif
				addFilesInt(path_lower, get_db_tgroup(), fs_files);
#ifndef _WIN32
			}
#endif
		}

		return fs_files;
	}
#ifdef _WIN32
	else
	{	
		if( cd->getFiles(path_lower, get_db_tgroup(), fs_files, target_generation) )
		{
			++index_c_db;

			handleSymlinks(orig_path, named_path, exclude_dirs, include_dirs, fs_files);

			if(calculate_filehashes_on_client)
			{
				if(addMissingHashes(&fs_files, NULL, orig_path, path, named_path,
					exclude_dirs, include_dirs, phash_queue==NULL))
				{
					++index_c_db_update;
					modifyFilesInt(path_lower, get_db_tgroup(), fs_files, target_generation);
					++target_generation;
				}
			}

			return fs_files;
		}
		else
		{
			++index_c_fs;

			std::string tpath=os_file_prefix(path);

			bool has_error;
			std::vector<SFile> os_files = getFilesWin(tpath, &has_error, true, true, (index_flags & EBackupDirFlag_OneFilesystem) > 0);
			filterEncryptedFiles(path, orig_path, os_files);
			fs_files=convertToFileAndHash(orig_path, named_path, exclude_dirs, include_dirs, os_files, fn_filter);
			if(has_error)
			{
				if(os_directory_exists(index_root_path))
				{
					VSSLog("Error while getting files in folder \""+path+"\". SYSTEM may not have permissions to access this folder. Windows errorcode: "+convert((int)GetLastError()), LL_ERROR);
				}
				else
				{
					VSSLog("Error while getting files in folder \""+path+"\". Windows errorcode: "+convert((int)GetLastError())+". Access to root directory is gone too. Shadow copy was probably deleted while indexing.", LL_ERROR);
					index_error=true;
				}
			}

			if(calculate_filehashes_on_client
				&& phash_queue==NULL)
			{
				addMissingHashes(NULL, &fs_files, orig_path, path, named_path,
					exclude_dirs, include_dirs, true);
			}

			addFilesInt(path_lower, get_db_tgroup(), fs_files);
			return fs_files;
		}
	}
#else //_WIN32
	return fs_files;
#endif
}

IPipe * IndexThread::getMsgPipe(void)
{
	return msgpipe;
}

void IndexThread::stopIndex(void)
{
	stop_index=true;
}

bool IndexThread::find_existing_shadowcopy(SCDirs *dir, bool *onlyref, bool allow_restart, bool simultaneous_other, const std::string& wpath,
	const std::vector<SCRef*>& no_restart_refs, bool for_imagebackup, bool *stale_shadowcopy, bool consider_only_own_tokens,
	bool share_new)
{
	for (size_t i = sc_refs.size(); i-- > 0;)
	{
		if (i >= sc_refs.size())
		{
			continue;
		}
#ifndef _WIN32
		std::string target_lower = sc_refs[i]->target;
		std::string wpath_lower = wpath;
#else
		std::string target_lower = strlower(sc_refs[i]->target);
		std::string wpath_lower = strlower(wpath);
#endif
		if(target_lower == wpath_lower && sc_refs[i]->ok
			&& sc_refs[i]->clientsubname == index_clientsubname )
		{
			bool do_restart = std::find(no_restart_refs.begin(),
				no_restart_refs.end(), sc_refs[i])==no_restart_refs.end();

			bool only_own_tokens=true;
			for(size_t k=0;k<sc_refs[i]->starttokens.size();++k)
			{
				int64 last_token_time = ClientConnector::getLastTokenTime(sc_refs[i]->starttokens[k]);
				int64 curr_time = Server->getTimeSeconds();
				bool token_timeout=true;
				if(curr_time>=last_token_time && curr_time-last_token_time<10*60*1000)
				{
					token_timeout=false;
				}
				if( sc_refs[i]->starttokens[k]!=starttoken && !token_timeout)
				{
					only_own_tokens=false;
					break;
				}
			}

			if(consider_only_own_tokens && !only_own_tokens)
				continue;


			bool cannot_open_shadowcopy = false;

#ifdef _WIN32
			IFile *volf=Server->openFile(sc_refs[i]->volpath, MODE_READ_DEVICE);
			if(volf==NULL)
			{
				cannot_open_shadowcopy=true;
			}
			else
			{
				Server->destroy(volf);
			}

#else
			cannot_open_shadowcopy = !os_directory_exists(sc_refs[i]->volpath);
#endif

			if(cannot_open_shadowcopy)
			{
				Server->Log("Could not open snapshot at \"" + sc_refs[i]->volpath + "\"", LL_WARNING);

				if(!do_restart)
				{
					VSSLog("Cannot open shadowcopy. Creating new or choosing other.", LL_WARNING);
					continue;
				}
				else
				{
					VSSLog("Removing reference because shadowcopy could not be openend", LL_WARNING);
				}
			}
			
			if( do_restart && allow_restart && (Server->getTimeSeconds()-sc_refs[i]->starttime>shadowcopy_startnew_timeout/1000
				|| only_own_tokens 
				|| cannot_open_shadowcopy ) )
			{
#ifndef _WIN32
				int64 wait_starttime = Server->getTimeMS();

				while (filesrv != nullptr
					&& filesrv->hasActiveTransfers(dir->dir, starttoken)
					&& Server->getTimeMS() - wait_starttime < 5000)
				{
					Server->wait(100);
				}

				if (filesrv != nullptr
					&& filesrv->hasActiveTransfers(dir->dir, starttoken))
				{
					sc_refs[i]->cleanup = true;
					sc_refs[i]->cleanup_gen = filesrv->incrActiveGeneration();
					sc_refs_cleanup = true;
					VSSLog("Old shadow copy of " + sc_refs[i]->target + " still in use. Not deleting or using it.", LL_WARNING);
					dir->target = dir->orig_target;
					continue;
				}
#endif

				if ( (sc_refs[i]->for_imagebackup == for_imagebackup)
					|| !simultaneous_other )
				{
					if (only_own_tokens)
					{
						VSSLog("Restarting shadow copy of " + sc_refs[i]->target + " because it was started by this server", LL_WARNING);
					}
					else if (!cannot_open_shadowcopy)
					{
						VSSLog("Restarting/not using already existing shadow copy of " + sc_refs[i]->target + " because it is too old", LL_INFO);
					}

					SCRef *curr = sc_refs[i];
					std::map<std::string, SCDirs*>& scdirs_server = scdirs[SCDirServerKey(starttoken, index_clientsubname, sc_refs[i]->for_imagebackup)];

					VSS_ID ssetid = curr->ssetid;

					std::vector<std::string> paths;
					for (std::map<std::string, SCDirs*>::iterator it = scdirs_server.begin();
						it != scdirs_server.end(); ++it)
					{
						paths.push_back(it->first);
					}

					for (size_t j = 0; j < paths.size(); ++j)
					{
						std::map<std::string, SCDirs*>::iterator it = scdirs_server.find(paths[j]);
						if (it != scdirs_server.end()
							&& it->second->ref == curr)
						{
							VSSLog("Releasing " + it->first + " orig_target=" + it->second->orig_target + " target=" + it->second->target, LL_DEBUG);
							release_shadowcopy(it->second, false, -1, dir);
						}
					}

					bool retry = true;
					while (retry)
					{
						retry = false;
						for (std::map<std::string, SCDirs*>::iterator it = scdirs_server.begin();
							it != scdirs_server.end();++it)
						{
							if (it->second->ref != nullptr
								&& it->second->ref != curr
								&& it->second->ref->ssetid == ssetid)
							{
								VSSLog("Releasing group shadow copy " + it->first + " orig_target=" + it->second->orig_target + " target=" + it->second->target, LL_DEBUG);
								release_shadowcopy(it->second, false, -1, dir);
								retry = true;
								break;
							}
						}
					}
					dir->target = dir->orig_target;
					continue;
				}
				else
				{
					VSSLog("Not restarting/using existing shadow copy of " + sc_refs[i]->target + 
						" because it was not created for image backups/file backups and there is a simultaneous other backup (for_imagebackup="+convert(for_imagebackup)+")", LL_INFO);
				}
			}
			else if(!cannot_open_shadowcopy)
			{
				sc_refs[i]->cleanup = false;
				dir->ref=sc_refs[i];
				if(!dir->ref->dontincrement)
				{
					sc_refs[i]->starttokens.push_back(starttoken);
				}
				else
				{
					dir->ref->dontincrement=false;
				}

				dir->ref->sharenames.push_back(dir->dir);

				VSSLog("orig_target="+dir->orig_target+" volpath="+dir->ref->volpath, LL_DEBUG);

				dir->target=dir->orig_target;
				dir->target.erase(0,wpath.size());

#ifndef _WIN32
				if(dir->target.empty() || dir->target[0]!='/')
				{
					dir->target = "/" + dir->target;
				}
				dir->target=dir->ref->volpath+ (dir->target.empty()? "" : dir->target);
#else
				if(for_imagebackup)
				{
					dir->target=dir->ref->volpath;
				}
				else
				{
					dir->target=dir->ref->volpath+os_file_sep()+dir->target;
				}
#endif
				if(dir->fileserv
					&& share_new)
				{
					shareDir(starttoken, dir->dir, dir->target);
				}

				if(for_imagebackup && dir->ref->save_id!=-1)
				{
					cd->modShadowcopyRefCount(dir->ref->save_id, 1);
				}

				if(onlyref!=nullptr)
				{
					*onlyref=true;
				}

				if( stale_shadowcopy!=nullptr )
				{
					if(!do_restart)
					{
						*stale_shadowcopy=false;
					}
					else if(!only_own_tokens || !allow_restart)
					{
						*stale_shadowcopy=true;
					}
				}

				VSSLog("Shadowcopy already present.", LL_DEBUG);
				return true;
			}
		}
	}

	return false;
}

bool IndexThread::start_shadowcopy(SCDirs *dir, bool *onlyref, bool allow_restart, bool simultaneous_other,
	std::vector<SCRef*> no_restart_refs, bool for_imagebackup, bool *stale_shadowcopy, bool* not_configured,
	bool* has_active_transaction)
{
	bool c_onlyref = false;
	if (onlyref != nullptr)
	{
		if (*onlyref)
		{
			c_onlyref = true;
		}
		else
		{
			*onlyref = true;
		}
	}

	cleanup_saved_shadowcopies(!simultaneous_other);

#ifdef _WIN32
	WCHAR volume_path[MAX_PATH];
	BOOL ok = GetVolumePathNameW(Server->ConvertToWchar(dir->orig_target).c_str(), volume_path, MAX_PATH);
	if (!ok)
	{
		VSSLog("Cannot get volume for path \""+ dir->orig_target+"\". "+os_last_error_str(), LL_ERROR);
		return false;
	}
	std::string wpath = Server->ConvertFromWchar(volume_path);
#else
	std::string wpath = "/";

	if (get_volumes_mounted_locally()
		&& !for_imagebackup)
	{
		wpath = getFolderMount(dir->orig_target);
		if (wpath.empty())
		{
			wpath = dir->orig_target;
		}
	}
	else
	{
		wpath = getDeviceMount(dir->orig_target);
		if(wpath.empty())
		{
			wpath = dir->orig_target;
		}
	}
#endif

	
	if(find_existing_shadowcopy(dir, onlyref, allow_restart, simultaneous_other, wpath, no_restart_refs, for_imagebackup, stale_shadowcopy, true, !c_onlyref)
		|| find_existing_shadowcopy(dir, onlyref, allow_restart, simultaneous_other, wpath, no_restart_refs, for_imagebackup, stale_shadowcopy, true, !c_onlyref) )
	{
		return true;
	}

	if (c_onlyref)
	{
		return false;
	}

	dir->ref=new SCRef;
	dir->ref->starttime=Server->getTimeSeconds();
	dir->ref->target=wpath;
	dir->ref->starttokens.push_back(starttoken);
	dir->ref->sharenames.push_back(dir->dir);
	dir->ref->clientsubname = index_clientsubname;
	dir->ref->for_imagebackup = for_imagebackup;
	sc_refs.push_back(dir->ref);
	

#ifdef _WIN32
	bool b = start_shadowcopy_win(dir, wpath, for_imagebackup, false, onlyref, has_active_transaction);
#else
	bool b = start_shadowcopy_lin(dir, wpath, for_imagebackup, onlyref, not_configured);
#endif

	if(!b)
	{
		sc_refs.erase(sc_refs.end()-1);
		delete dir->ref;
		dir->ref=nullptr;
		dir->target = dir->orig_target;
	}

	return b;
}

namespace
{
	void cleanup_shadowcopies_xp(ClientDAO *cd, SCDirs *dir)
	{
#if defined(VSS_XP) || defined(VSS_S03)
		std::vector<SShadowCopy> scs=cd->getShadowcopies();

		bool found=false;

		for(size_t i=0;i<scs.size();++i)
		{
			if( scs[i].target==dir->target || ( dir->ref!=NULL && dir->ref->backupcom==NULL )  )
			{
				found=true;
			}
		}

		if(found)
		{
			for(size_t i=0;i<scs.size();++i)
			{
				if(scs[i].target==dir->target || ( dir->ref!=NULL && dir->ref->backupcom==NULL ) )
				{
					Server->Log("Removing shadowcopy entry for path \""+scs[i].path+"\"", LL_DEBUG);
					cd->deleteShadowcopy(scs[i].id);
				}
			}
		}
#endif
	}
}


bool IndexThread::deleteShadowcopy(SCDirs *dir)
{
#ifdef _WIN32
	return deleteShadowcopyWin(dir);
#else

	for (size_t i = 0; i < dir->ref->flock_fds.size(); ++i)
		close(dir->ref->flock_fds[i]);

	dir->ref->flock_fds.clear();

	std::string loglines;
	std::string scriptname;
	if(dir->fileserv)
	{
		scriptname = "remove_filesystem_snapshot";
	}
	else
	{
		scriptname = "remove_volume_snapshot";
	}

	std::string scriptlocation = get_snapshot_script_location(scriptname, index_clientsubname);

	if (scriptlocation.empty())
	{
		return false;
	}

	int rc = os_popen(scriptlocation +" "+guidToString(dir->ref->ssetid)+" "+escapeDirParam(dir->ref->volpath)
		+" "+escapeDirParam(dir->dir)+" "+escapeDirParam(dir->target)+" "+escapeDirParam(dir->ref->target)
		+ (dir->ref->clientsubname.empty() ? "" : (" " + escapeDirParam(dir->ref->clientsubname)))+" 2>&1", loglines);
	if(rc!=0)
	{
		VSSLog("Error removing snapshot to "+dir->target, LL_ERROR);
		VSSLogLines(loglines, LL_ERROR);
		return false;
	}
	else
	{
		VSSLogLines(loglines, LL_INFO);
	}
	return true;
#endif
}

bool IndexThread::release_shadowcopy(SCDirs *dir, bool for_imagebackup, int save_id, SCDirs *dontdel)
{
	if(for_imagebackup)
	{
		if(dir->ref!=nullptr && dir->ref->save_id!=-1)
		{
			cd->modShadowcopyRefCount(dir->ref->save_id, -1);
		}
		else if(save_id!=-1)
		{
			cd->modShadowcopyRefCount(save_id, -1);
		}
	}

	bool ok=true;

	if(dir->ref!=nullptr 
#ifdef _WIN32
		&& dir->ref->backupcom!=NULL
#endif
		)
	{
		if(dir->ref->starttokens.empty() ||
			( dir->ref->starttokens.size()==1
				&& dir->ref->starttokens[0]==starttoken)
			|| Server->getTimeSeconds()-dir->ref->starttime>shadowcopy_timeout/1000)
		{

			VSSLog("Deleting shadowcopy for path \""+dir->target+"\" -2", LL_DEBUG);
			ok = deleteShadowcopy(dir);

			if(dir->ref->save_id!=-1)
			{
				cd->deleteShadowcopy(dir->ref->save_id);
			}
		}
	}

	if(dir->ref!=nullptr)
	{
		for(size_t k=0;k<dir->ref->starttokens.size();++k)
		{
			if(dir->ref->starttokens[k]==starttoken)
			{
				dir->ref->starttokens.erase(dir->ref->starttokens.begin()+k);
				break;
			}
		}
	}

	cleanup_saved_shadowcopies();

	cleanup_shadowcopies_xp(cd, dir);
	

	bool r=true;
	while(r)
	{
		r=false;
		for(size_t i=0;i<sc_refs.size();++i)
		{
			if(sc_refs[i]->starttokens.empty())
			{
				VSSLog("Deleting Shadowcopy for dir \""+sc_refs[i]->target+"\"", LL_DEBUG);
				bool c=true;
				while(c)
				{
					c=false;
					for(std::map<SCDirServerKey, std::map<std::string, SCDirs*> >::iterator server_it = scdirs.begin();
						server_it!=scdirs.end();++server_it)
					{
						for(std::map<std::string, SCDirs*>::iterator it=server_it->second.begin();
							it!=server_it->second.end();++it)
						{
							if(it->second->ref==sc_refs[i])
							{
								if(it->second->fileserv)
								{
									shareDir(server_it->first.start_token, it->second->dir, it->second->orig_target);
								}
								it->second->target=it->second->orig_target;

								it->second->ref=nullptr;
								if(dontdel==nullptr || it->second!=dontdel )
								{
									delete it->second;
									server_it->second.erase(it);
									c=true;
									break;
								}
							}
						}
					}					
				}
				delete sc_refs[i];
				sc_refs.erase(sc_refs.begin()+i);
				r=true;
				break;
			}
		}
	}

	return ok;
}


bool IndexThread::deleteSavedShadowCopy( SShadowCopy& scs, SShadowCopyContext& context )
{
#if defined(_WIN32) && !defined(VSS_XP) && !defined(VSS_S03)
	return deleteSavedShadowCopyWin(scs, context);
#elif !defined(_WIN32)
	std::string loglines;

	std::string scriptname;
	if(scs.filesrv)
	{
		scriptname = "remove_filesystem_snapshot";
	}
	else
	{
		scriptname = "remove_volume_snapshot";
	}

	std::string scriptlocation = get_snapshot_script_location(scriptname, index_clientsubname);

	if (scriptlocation.empty())
	{
		return false;
	}

	int rc = os_popen(scriptlocation + " "+guidToString(scs.ssetid)+" "+escapeDirParam(scs.path)+" "+escapeDirParam(scs.tname)
		+" "+escapeDirParam(scs.path)+" "+escapeDirParam(scs.orig_target)
		+ (scs.clientsubname.empty() ? "" : (" " + escapeDirParam(scs.clientsubname))), loglines);
	if(rc!=0)
	{
		VSSLog("Error removing snapshot to "+scs.orig_target, LL_ERROR);
		VSSLogLines(loglines, LL_ERROR);
		return false;
	}
	else
	{
		cd->deleteShadowcopy(scs.id);
		VSSLogLines(loglines, LL_INFO);
	}
	return true;
#else
	cd->deleteShadowcopy(scs.id);
	return true;
#endif
}

bool IndexThread::cleanup_saved_shadowcopies(bool start)
{
	std::vector<SShadowCopy> scs=cd->getShadowcopies();

	SShadowCopyContext context;

	bool ok=true;
	for(size_t i=0;i<scs.size();++i)
	{
		bool f2=true;
		for(size_t j=0;j<sc_refs.size();++j)
		{
			if(sc_refs[j]->save_id==scs[i].id
				|| sc_refs[j]->ssetid==scs[i].ssetid)
			{
				f2=false;
				break;
			}
		}
		if(f2 && (scs[i].refs<=0
			|| scs[i].passedtime>shadowcopy_timeout/1000
			|| (start && !scs[i].filesrv && scs[i].refs==1 && !starttoken.empty() && scs[i].starttoken==starttoken && scs[i].clientsubname==index_clientsubname ) ) )
		{
			if (!deleteSavedShadowCopy(scs[i], context))
			{
				ok = false;
			}
		}
	}

	clearContext(context);

	return ok;
}



SCDirs* IndexThread::getSCDir(const std::string& path, const std::string& clientsubname, bool for_imagebackup)
{
	std::map<std::string, SCDirs*>& scdirs_server = scdirs[SCDirServerKey(starttoken, clientsubname, for_imagebackup)];
	std::map<std::string, SCDirs*>::iterator it=scdirs_server.find(path);
	if(it!=scdirs_server.end())
	{
		if (it->second->ref != nullptr)
			it->second->ref->cleanup = false;

		return it->second;
	}
	else
	{
		SCDirs *nd=new SCDirs;
		scdirs_server.insert(std::pair<std::string, SCDirs*>(path, nd) );

		nd->running=false;

		return nd;
	}
}

IFileServ *IndexThread::getFileSrv(void)
{
	IScopedLock lock(filesrv_mutex);
	return filesrv;
}

int IndexThread::execute_hook(std::string script_name, bool incr, std::string server_token, int* index_group, int* error_info)
{
	if (!FileExists(script_name))
	{
		Server->Log("Script \"" + script_name + "\" does not exist", LL_DEBUG);
		return 0;
	}

	server_token = greplace("\"", "", server_token);
	server_token = greplace("\\", "", server_token);

	
#ifdef _WIN32
	//I ... waaa ... even ... :(
	std::string quoted_script_name = greplace(" ", "\" \"", script_name);
#else
	std::string quoted_script_name = "\"" + greplace("\"", "\\\"", script_name) + "\"";
#endif

	std::string output;
	int rc = os_popen(quoted_script_name + " " + (incr ? "1" : "0") + " \"" 
		+ server_token + "\" "
		+ (index_group!=nullptr ? convert(*index_group) : "" )
		+ (error_info!=nullptr ? convert(*error_info) : "" )
		+" 2>&1", output);

	if (rc != 0 && !output.empty())
	{
		Server->Log("Script \"" + script_name + "\" returned with error code " + convert(rc), LL_WARNING);
		VSSLogLines(output, LL_ERROR);
	}
	else if (!output.empty())
	{
		Server->Log("Script \"" + script_name + "\" returned with success", LL_INFO);
		VSSLogLines(output, LL_INFO);
	}

	return rc;
}

int IndexThread::execute_prebackup_hook(bool incr, std::string server_token, int index_group)
{
	std::string script_name;
#ifdef _WIN32
	script_name = Server->getServerWorkingDir() + "\\prefilebackup.bat";

	if (getFile(script_name)
		==":: Commands in this file will be executed before each incremental or full file backup")
	{
		return 0;
	}
#else
	script_name = SYSCONFDIR "/urbackup/prefilebackup";
#endif

	return execute_hook(script_name, incr, server_token, &index_group);
}

int IndexThread::execute_postindex_hook(bool incr, std::string server_token, int index_group, IndexErrorInfo error_info)
{
	std::string script_name;
#ifdef _WIN32
	script_name = Server->getServerWorkingDir() + "\\postfileindex.bat";
#else
	script_name = SYSCONFDIR "/urbackup/postfileindex";
#endif

	int i_error_info = static_cast<int>(error_info);

	return execute_hook(script_name, incr, server_token, &index_group, &i_error_info);
}

void IndexThread::execute_postbackup_hook(std::string scriptname, int group, const std::string& clientsubname)
{
#ifdef _WIN32
	STARTUPINFOW si;
	PROCESS_INFORMATION pi;
	memset(&si, 0, sizeof(STARTUPINFO) );
	memset(&pi, 0, sizeof(PROCESS_INFORMATION) );
	si.cb=sizeof(STARTUPINFO);

	std::string clientsubname_san = greplace("\"", "", clientsubname);
	clientsubname_san = greplace("\\", "", clientsubname_san);

	std::string quoted_script_name = greplace(" ", "\" \"", Server->getServerWorkingDir() + "\\" + scriptname + ".bat");

	if (!CreateProcessW(L"C:\\Windows\\system32\\cmd.exe", (LPWSTR)(Server->ConvertToWchar("cmd.exe /C " + quoted_script_name + " " + convert(group) + " \"" + clientsubname_san + "\"")).c_str()
		, NULL, NULL, false, NORMAL_PRIORITY_CLASS | CREATE_NO_WINDOW, NULL, NULL, &si, &pi))
	{
		Server->Log("Executing postfilebackup.bat failed: "+convert((int)GetLastError()), LL_INFO);
	}
	else
	{
		CloseHandle(pi.hProcess);
		CloseHandle(pi.hThread);
	}
#else
	pid_t pid1;
	pid1 = fork();
	if( pid1==0 )
	{
		setsid();
		pid_t pid2;
		pid2 = fork();
		if(pid2==0)
		{
			std::string fullname = std::string(SYSCONFDIR "/urbackup/") + scriptname;
			std::string group_str = convert(group);
			char* const argv[]={ const_cast<char*>(fullname.c_str()), 
				const_cast<char*>(group_str.c_str()), const_cast<char*>(clientsubname.c_str()), nullptr };
			execv(const_cast<char*>(fullname.c_str()), argv);
			exit(1);
		}
		else
		{
			exit(1);
		}
	}
	else
	{
		int status;
		waitpid(pid1, &status, 0);
	}
#endif
}

std::string IndexThread::sanitizePattern(const std::string &p)
{
	std::string ep=trim(p);
	std::string nep;
	nep.reserve(ep.size()*2);
	for(size_t j=0;j<ep.size();++j)
	{
		char ch=ep[j];
		if(ch=='/')
		{
			if(os_file_sep()=="\\")
			{
				nep+="\\\\";
			}
			else
			{
				nep+=os_file_sep();
			}
		}
		else if(ch=='\\' && j+1<ep.size() && ep[j+1]=='\\')
		{
			if(os_file_sep()=="\\")
			{
				nep+="\\\\";
			}
			else
			{
				nep+=os_file_sep();
			}
			++j;
		}
		else if(ch=='\\')
		{
			if(os_file_sep()=="\\")
				nep+="\\\\";
			else
				nep+=os_file_sep();
		}
		else
		{
			nep+=ch;
		}
	}
	return nep;
}

void IndexThread::readPatterns(int facet_id, int index_group, std::string index_clientsubname,
	std::vector<std::string>& exclude_dirs, std::vector<SIndexInclude>& include_dirs,
	bool& backup_dirs_optional)
{
	backup_dirs_optional = false;

	std::string exclude_pattern_key = "exclude_files";
	std::string include_pattern_key = "include_files";

	if(index_group==c_group_continuous)
	{
		exclude_pattern_key="continuous_exclude_files";
		include_pattern_key="continuous_include_files";
	}

	std::string settings_fn = "urbackup/data_"+std::to_string(facet_id)+"/settings.cfg";
	if(!index_clientsubname.empty())
	{
		settings_fn = "urbackup/data" + std::to_string(facet_id) + "/settings_"+conv_filename(index_clientsubname)+".cfg";
	}

	ISettingsReader *curr_settings=Server->createFileSettingsReader(settings_fn);
	exclude_dirs.clear();
	if(curr_settings!=nullptr)
	{	
		std::string val;
		if(curr_settings->getValue(exclude_pattern_key, &val) || curr_settings->getValue(exclude_pattern_key+"_def", &val) )
		{
			exclude_dirs = buildExcludeList(val);
		}
		else
		{
			exclude_dirs = buildExcludeList(std::string());
		}

		if(curr_settings->getValue(include_pattern_key, &val) || curr_settings->getValue(include_pattern_key+"_def", &val) )
		{
			include_dirs = parseIncludePatterns(val);
		}

		if (curr_settings->getValue("backup_dirs_optional", &val) || curr_settings->getValue("backup_dirs_optional_def", &val))
		{
			backup_dirs_optional = val == "true";
		}

		Server->destroy(curr_settings);
	}
	else
	{
		exclude_dirs = buildExcludeList(std::string());
	}
}

void IndexThread::onReadError(const std::string& sharename, const std::string& filepath, int64 pos, const std::string& msg)
{
	SReadError read_error;
	read_error.sharename = sharename;
	read_error.filepath = filepath;
	read_error.filepos = pos;
	read_error.msg = msg;

	IScopedLock lock(read_error_mutex);

	if (std::find(read_errors.begin(), read_errors.end(), read_error)==read_errors.end())
	{
		read_errors.push_back(read_error);
	}
}

void IndexThread::deregisterFileSrvScriptFn(const std::string & fn)
{
	filesrv->deregisterScriptPipeFile(fn);
}

unsigned int IndexThread::getResultId()
{
	IScopedLock lock(result_mutex);
	unsigned int ret= next_result_id++;
	if (ret == 0)
		ret = next_result_id++;

	SResult res;
	res.finished = false;
	res.refs = 1;
	res.cond = Server->createCondition();
	index_results.insert(std::make_pair(ret, res));
	return ret;
}

bool IndexThread::getResult(unsigned int id, int timeoutms, std::string & result)
{
	IScopedLock lock(result_mutex);

	std::map<unsigned int, SResult>::iterator it = index_results.find(id);

	if (it == index_results.end())
		return false;

	if ( (timeoutms > 0 || timeoutms == -1)
		&& it->second.results.empty())
	{
		it->second.cond->wait(&lock, timeoutms);
	}

	if (!it->second.results.empty())
	{
		result = it->second.results[0];
		it->second.results.erase(it->second.results.begin());
	}
	else
	{
		result.clear();
	}

	return true;
}

bool IndexThread::refResult(unsigned int id)
{
	if (id == 0)
		return false;

	IScopedLock lock(result_mutex);

	std::map<unsigned int, SResult>::iterator it = index_results.find(id);

	if (it == index_results.end())
		return false;

	++it->second.refs;

	return true;
}

bool IndexThread::unrefResult(unsigned int id)
{
	if (id == 0)
		return false;

	IScopedLock lock(result_mutex);

	std::map<unsigned int, SResult>::iterator it = index_results.find(id);

	if (it == index_results.end())
		return false;

	if (it->second.refs > 0)
	{
		--it->second.refs;
	}

	if (it->second.refs == 0
		&& it->second.finished)
	{
		Server->destroy(it->second.cond);
		index_results.erase(it);
	}

	return true;
}

void IndexThread::removeResult(unsigned int id)
{
	IScopedLock lock(result_mutex);

	std::map<unsigned int, SResult>::iterator it = index_results.find(id);

	if (it != index_results.end())
	{
		Server->destroy(it->second.cond);
		index_results.erase(it);
	}
}

std::vector<std::string> IndexThread::buildExcludeList(const std::string& val)
{
	std::vector<std::string> exclude_dirs_combined;
	exclude_dirs_combined = parseExcludePatterns(val);
	addFileExceptions(exclude_dirs_combined);
	addHardExcludes(exclude_dirs_combined);
#ifdef __APPLE__
	addMacOSExcludes(exclude_dirs_combined);
#endif
	return exclude_dirs_combined;
}

std::vector<std::string> IndexThread::parseExcludePatterns(const std::string& val)
{
	std::vector<std::string> exclude_dirs;
	if(!val.empty())
	{
		std::vector<std::string> toks;
		Tokenize(val, toks, ";");
		exclude_dirs =toks;
#ifdef _WIN32
		for(size_t i=0;i<exclude_dirs.size();++i)
		{
			strupper(&exclude_dirs[i]);
		}
#endif
		for(size_t i=0;i<exclude_dirs.size();++i)
		{
			if(exclude_dirs[i].find('\\')==std::string::npos
				&& exclude_dirs[i].find('/')==std::string::npos
				&& exclude_dirs[i].find('*')==std::string::npos )
			{
				exclude_dirs[i]="*/"+trim(exclude_dirs[i]);
			}
		}
		for(size_t i=0;i<exclude_dirs.size();++i)
		{
			exclude_dirs[i]=sanitizePattern(exclude_dirs[i]);
		}
	}
	return exclude_dirs;
}

std::vector<SIndexInclude> IndexThread::parseIncludePatterns(const std::string& val)
{
	std::vector<std::string> toks;
	Tokenize(val, toks, ";");
	std::vector<SIndexInclude> include_dirs(toks.size());
	for(size_t i=0;i<include_dirs.size();++i)
	{
		include_dirs[i].spec = toks[i];
#ifdef _WIN32
		strupper(&include_dirs[i].spec);
#endif
	}
	for(size_t i=0;i<include_dirs.size();++i)
	{
		include_dirs[i].spec=sanitizePattern(include_dirs[i].spec);
	}

	for(size_t i=0;i<include_dirs.size();++i)
	{
		std::string ip=include_dirs[i].spec;
		if(ip.find("*")==ip.size()-1 || ip.find("*")==std::string::npos)
		{
			int depth=0;
			for(size_t j=0;j<ip.size();++j)
			{
				if(ip[j]=='/')
					++depth;
				else if(ip[j]=='\\' && j+1<ip.size() && ip[j+1]=='\\')
				{
					++j;
					++depth;
				}
			}
			include_dirs[i].depth=depth;
		}
		else
		{
			include_dirs[i].depth =-1;
		}
	}

	for(size_t i=0;i<include_dirs.size();++i)
	{
		size_t f1=include_dirs[i].spec.find_first_of(":");
		size_t f2=include_dirs[i].spec.find_first_of("[");
		size_t f3=include_dirs[i].spec.find_first_of("*");
		while(f2>0 && f2!=std::string::npos && include_dirs[i].spec[f2-1]=='\\')
			f2=include_dirs[i].spec.find_first_of("[", f2);

		size_t f=(std::min)((std::min)(f1,f2), f3);

		if(f!=std::string::npos)
		{
			if(f>0)
			{
				include_dirs[i].prefix=include_dirs[i].prefix.substr(0, f);
			}
		}
		else
		{
			include_dirs[i].prefix= include_dirs[i].prefix;
		}

		std::string nep;
		for(size_t j=0;j<include_dirs[i].prefix.size();++j)
		{
			char ch= include_dirs[i].prefix[j];
			if(ch=='/')
				nep+=os_file_sep();
			else if(ch=='\\' && j+1<include_dirs[i].prefix.size() && include_dirs[i].prefix[j+1]=='\\')
			{
				nep+=os_file_sep();
				++j;
			}
			else
			{
				nep+=ch;
			}
		}			

		include_dirs[i].prefix=nep;
	}

	return include_dirs;
}

bool IndexThread::isExcluded(const std::vector<std::string>& exclude_dirs, const std::string &path)
{
	std::string wpath=path;
#ifdef _WIN32
	strupper(&wpath);
#endif
	for(size_t i=0;i<exclude_dirs.size();++i)
	{
		if(!exclude_dirs[i].empty())
		{
			bool b=amatch(wpath.c_str(), exclude_dirs[i].c_str());
			if(b)
			{
#ifdef __APPLE__
                logMacOSExclude(path);
#endif
				return true;
			}
		}
	}
#ifdef __APPLE__
    if(isExcludedByXattr(path))
    {
        logIsExcludedByXattr(path);
        return true;
    };
#endif
	return false;
}

std::vector<std::string> IndexThread::getRmExcludedByPatterns(std::vector<std::string>& exclude_dirs, const std::string & path)
{
	std::string wpath = path;
#ifdef _WIN32
	strupper(&wpath);
#endif
	std::vector<std::string> ret;
	for (size_t i = 0; i<exclude_dirs.size();)
	{
		if (!exclude_dirs[i].empty())
		{
			bool b = amatch(wpath.c_str(), exclude_dirs[i].c_str());
			if (b)
			{
				ret.push_back(exclude_dirs[i]);
				exclude_dirs.erase(exclude_dirs.begin() + i);
				continue;
			}
		}
		++i;
	}
	return ret;
}

bool IndexThread::isIncluded(const std::vector<SIndexInclude>& include_dirs, const std::string &path, bool *adding_worthless)
{
	std::string wpath=path;
#ifdef _WIN32
	strupper(&wpath);
#endif
	int wpath_level=0;
	if(adding_worthless!=nullptr)
	{
		for(size_t i=0;i<wpath.size();++i)
		{
			if(wpath[i]=='/')
				++wpath_level;
			else if(wpath[i]=='\\')
				++wpath_level;
			else if(i==wpath.size()-1)
				++wpath_level;
		}
		*adding_worthless=true;
	}
	bool has_pattern=false;
	for(size_t i=0;i<include_dirs.size();++i)
	{
		if(!include_dirs[i].spec.empty())
		{
			has_pattern=true;
			bool b=amatch(wpath.c_str(), include_dirs[i].spec.c_str());
			if(b)
			{
				return true;
			}
			if(adding_worthless!=nullptr)
			{
				if( include_dirs[i].depth==-1 )
				{
					*adding_worthless=false;
				}
				else
				{
					bool has_prefix=(wpath.find(include_dirs[i].prefix)==0);
					if( has_prefix )
					{
						if( wpath_level<= include_dirs[i].depth)
						{
							*adding_worthless=false;
						}
					}
				}
			}
		}
	}
	return !has_pattern;
}

void IndexThread::start_filesrv(void)
{
	std::string name;
	if(Server->getServerParameter("restore_mode")=="true")
	{
		name="##restore##"+convert(Server->getTimeSeconds())+convert(Server->getRandomNumber()%10000);
		writestring((name), "clientname.txt");
	}
	else
	{
		ISettingsReader *curr_settings=Server->createFileSettingsReader("urbackup/data/settings.cfg");
		if(curr_settings!=nullptr)
		{
			std::string val;
			if(curr_settings->getValue("computername", &val)
				|| curr_settings->getValue("computername_def", &val) )
			{
				if(!val.empty())
				{
					name=val;
				}
			}
			Server->destroy(curr_settings);
		}
	}

	unsigned int curr_tcpport = tcpport;
	unsigned int curr_udpport = udpport;
	std::string s_tcpport=Server->getServerParameter("fileserv_tcpport");
	if(!s_tcpport.empty())
		curr_tcpport=atoi(s_tcpport.c_str());
	std::string s_udpport=Server->getServerParameter("fileserv_udpport");
	if(!s_udpport.empty())
		curr_udpport=atoi(s_udpport.c_str());

	bool use_fqdn=false;
	IDatabase* db = Server->getDatabase(Server->getThreadID(), URBACKUPDB_CLIENT);
	if(db)
	{
		db_results res = db->Read("SELECT tvalue FROM misc WHERE tkey = 'use_fqdn'");
		if(!res.empty() && res[0]["tvalue"]=="1")
		{
			use_fqdn=true;
		}
	}

	if(Server->getServerParameter("internet_only_mode")=="true")
	{
		curr_tcpport=0;
		curr_udpport=0;
	}

	IFileServFactory* filesrv_fak = (IFileServFactory*)Server->getPlugin(Server->getThreadID(), filesrv_pluginid);

	filesrv=filesrv_fak->createFileServ(curr_tcpport, curr_udpport, name, use_fqdn,
		backgroundBackupsEnabled(std::string()));

	ServerIdentityMgr::setFileServ(filesrv);
	ServerIdentityMgr::loadServerIdentities();

	filesrv->registerReadErrorCallback(this);
}

void IndexThread::shareDir(const std::string& token, std::string name, const std::string &path)
{
	if(!token.empty())
	{
		name = token + "|" +name;
	}

	if (name == "urbackup"
		|| name == "urbackup_backup_scripts")
	{
		Server->Log("Share named \"" + name + "\". Skipping...", LL_DEBUG);
		return;
	}

	IScopedLock lock(filesrv_mutex);
	filesrv_share_dirs[name]=path;
}

void IndexThread::removeDir(const std::string& token, std::string name)
{
	if(!token.empty())
	{
		name = token + "|" +name;
	}
	IScopedLock lock(filesrv_mutex);
	std::map<std::string, std::string>::iterator it=filesrv_share_dirs.find(name);
	if(it!=filesrv_share_dirs.end())
	{
		filesrv_share_dirs.erase(it);
	}
}

std::string IndexThread::getShareDir(const std::string &name)
{
	IScopedLock lock(filesrv_mutex);
	return filesrv_share_dirs[name];
}

void IndexThread::share_dirs()
{
	IScopedLock lock(filesrv_mutex);
	for (std::map<std::string, std::string>::iterator it = filesrv_share_dirs.begin(); it != filesrv_share_dirs.end(); ++it)
	{
		std::string dir = it->first;
		filesrv->shareDir(dir, it->second, std::string(), false);
	}
	filesrv->clearReadErrors();
}

void IndexThread::unshare_dirs()
{
	IScopedLock lock(filesrv_mutex);
	for(std::map<std::string, std::string>::iterator it=filesrv_share_dirs.begin();it!=filesrv_share_dirs.end();++it)
	{
		std::string dir=it->first;
		filesrv->removeDir(dir, std::string());
	}
}

void IndexThread::doStop(void)
{
	CWData wd;
	wd.addChar(IndexThreadAction_Stop);
	wd.addUInt(0);
	msgpipe->Write(wd.getDataPtr(), wd.getDataSize());
}


size_t IndexThread::calcBufferSize( std::string &path, const std::vector<SFileAndHash> &data )
{
	size_t add_size=path.size()+sizeof(std::string)+sizeof(int)+sizeof(int64);
	for(size_t i=0;i<data.size();++i)
	{
		add_size+=data[i].name.size();
		add_size+=sizeof(SFileAndHash);
		add_size+=data[i].hash.size();
	}
	add_size+=sizeof(std::vector<SFile>);

	return add_size;
}


void IndexThread::modifyFilesInt(std::string path, int tgroup,
	const std::vector<SFileAndHash> &data, int64 target_generation)
{
	modify_file_buffer_size+=calcBufferSize(path, data);

	modify_file_buffer.push_back(SBufferItem(path, tgroup, data, target_generation));

	if(last_file_buffer_commit_time==0)
	{
		last_file_buffer_commit_time = Server->getTimeMS();
	}

	if( modify_file_buffer_size+add_file_buffer_size>max_file_buffer_size 
		|| Server->getTimeMS()-last_file_buffer_commit_time>file_buffer_commit_interval)
	{
		commitModifyFilesBuffer();
		commitAddFilesBuffer();
		commitPhashQueue();
	}
}

void IndexThread::commitModifyFilesBuffer(void)
{
	db->BeginWriteTransaction();
	for(size_t i=0;i<modify_file_buffer.size();++i)
	{
		cd->modifyFiles(modify_file_buffer[i].path, modify_file_buffer[i].tgroup,
			modify_file_buffer[i].files, modify_file_buffer[i].target_generation);
	}
	db->EndTransaction();

	modify_file_buffer.clear();
	modify_file_buffer_size=0;
	last_file_buffer_commit_time=Server->getTimeMS();
}

void IndexThread::addFilesInt( std::string path, int tgroup, const std::vector<SFileAndHash> &data )
{
	add_file_buffer_size+=calcBufferSize(path, data);

	add_file_buffer.push_back(SBufferItem(path, tgroup, data, 0));

	if(last_file_buffer_commit_time==0)
	{
		last_file_buffer_commit_time = Server->getTimeMS();
	}

	if(add_file_buffer_size+ add_file_buffer_size>max_file_buffer_size
		|| Server->getTimeMS()-last_file_buffer_commit_time>file_buffer_commit_interval)
	{
		commitAddFilesBuffer();
		commitModifyFilesBuffer();
		commitPhashQueue();
	}

}

void IndexThread::commitAddFilesBuffer()
{
	db->BeginWriteTransaction();
	for(size_t i=0;i<add_file_buffer.size();++i)
	{
		cd->addFiles(add_file_buffer[i].path, add_file_buffer[i].tgroup, add_file_buffer[i].files,
			add_file_buffer[i].target_generation);
	}
	db->EndTransaction();

	add_file_buffer.clear();
	add_file_buffer_size=0;
	last_file_buffer_commit_time=Server->getTimeMS();
}


std::string IndexThread::removeDirectorySeparatorAtEnd(const std::string& path)
{
	char path_sep=os_file_sep()[0];
	if(!path.empty() && path[path.size()-1]==path_sep )
	{
		return path.substr(0, path.size()-1);
	}
	return path;
}

std::string IndexThread::addDirectorySeparatorAtEnd(const std::string& path)
{
	char path_sep=os_file_sep()[0];
	if(!path.empty() && path[path.size()-1]!=path_sep )
	{
		return path+os_file_sep();
	}
	return path;
}

std::string IndexThread::getSHA256(const std::string& fn)
{
	sha256_ctx ctx;
	sha256_init(&ctx);

	IFile * f=Server->openFile(os_file_prefix(fn), MODE_READ_SEQUENTIAL_BACKUP);

	if(f==nullptr)
	{
		return std::string();
	}

	int64 fsize = f->Size();
	int64 fpos = 0;

	char buffer[32768];
	unsigned int r;
	while(fpos<fsize)
	{
		_u32 max_read = static_cast<_u32>((std::min)(static_cast<int64>(32768), fsize - fpos));
		r = f->Read(buffer, max_read);

		if (r == 0)
		{
			break;
		}

		sha256_update(&ctx, reinterpret_cast<const unsigned char*>(buffer), r);

		if(IdleCheckerThread::getPause())
		{
			Server->wait(5000);
		}

		fpos += r;
	}

	Server->destroy(f);

	unsigned char dig[32];
	sha256_final(&ctx, dig);

	return bytesToHex(dig, 32);
}

void IndexThread::VSSLog(const std::string& msg, int loglevel)
{
	Server->Log(msg, loglevel);
	if(loglevel>LL_DEBUG)
	{
		SVssLogItem item;
		item.msg = msg;
		item.loglevel = loglevel;
		item.times = Server->getTimeSeconds();
		vsslog.push_back(item);
	}
}

void IndexThread::VSSLogLines(const std::string& msg, int loglevel)
{
	std::vector<std::string> lines;
	Tokenize(msg, lines, "\n");

	for(size_t i=0;i<lines.size();++i)
	{
		std::string line = trim(lines[i]);
		if(!line.empty())
		{
			VSSLog(line, loglevel);
		}
	}
}


#ifdef _WIN32
namespace
{
	LONG GetStringRegKey(HKEY hKey, const std::string &strValueName, std::string &strValue, const std::string &strDefaultValue)
	{
		strValue = strDefaultValue;
		WCHAR szBuffer[8192];
		DWORD dwBufferSize = sizeof(szBuffer);
		ULONG nError;
		std::wstring rval;
		nError = RegQueryValueExW(hKey, Server->ConvertToWchar(strValueName).c_str(), 0, NULL, (LPBYTE)szBuffer, &dwBufferSize);
		if (ERROR_SUCCESS == nError)
		{
			rval.resize(dwBufferSize/sizeof(wchar_t));
			memcpy(const_cast<wchar_t*>(rval.c_str()), szBuffer, dwBufferSize);
			strValue = Server->ConvertFromWchar(rval);
		}
		return nError;
	}
}
#endif

void IndexThread::addFileExceptions(std::vector<std::string>& exclude_dirs)
{
#ifdef _WIN32
	exclude_dirs.push_back(sanitizePattern("C:\\HIBERFIL.SYS"));

	HKEY hKey;
	LONG lRes = RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Memory Management", 0, KEY_READ, &hKey);
	if(lRes != ERROR_SUCCESS)
		return;

	std::string tfiles;
	lRes = GetStringRegKey(hKey, "ExistingPageFiles", tfiles, "");
	if(lRes != ERROR_SUCCESS)
		return;

	std::vector<std::string> toks;
	std::string sep;
	sep.resize(1);
	sep[0]=0;
	Tokenize(tfiles, toks, sep);
	for(size_t i=0;i<toks.size();++i)
	{
		toks[i]=trim(toks[i].c_str());

		if(toks[i].empty())
			continue;

		toks[i]=trim(toks[i]);

		if(toks[i].find("\\??\\")==0)
		{
			toks[i].erase(0, 4);
		}

		strupper(&toks[i]);

		exclude_dirs.push_back(sanitizePattern(toks[i]));
	}

	char* systemdrive = NULL;
	size_t bufferCount;
	if (_dupenv_s(&systemdrive, &bufferCount, "SystemDrive") == 0)
	{
		std::string excl = std::string(systemdrive) + "\\swapfile.sys";
		strupper(&excl);
		exclude_dirs.push_back(sanitizePattern(excl));
	}
	free(systemdrive);
#endif
}

void IndexThread::addHardExcludes(std::vector<std::string>& exclude_dirs)
{
#ifdef __linux__
	exclude_dirs.push_back("/proc/*");
	exclude_dirs.push_back("/dev/*");
	exclude_dirs.push_back("/sys/*");
	exclude_dirs.push_back("*/.datto_3d41c58e-6724-4d47-8981-11c766a08a24_:");
	exclude_dirs.push_back("*/.overlay_2fefd007-3e48-4162-b2c6-45ccdda22f37_:");
	exclude_dirs.push_back("/run/*");
#endif

#ifdef __FreeBSD__
	exclude_dirs.push_back("/dev/*");
	exclude_dirs.push_back("/proc/*");
#endif

#ifdef __APPLE__
	exclude_dirs.push_back("/dev/*");
#endif

#ifdef _WIN32
	exclude_dirs.push_back(sanitizePattern(":\\SYSTEM VOLUME INFORMATION\\URBCT.DAT"));
	exclude_dirs.push_back(sanitizePattern(":\\SYSTEM VOLUME INFORMATION\\*{3808876B-C176-4E48-B7AE-04046E6CC752}*"));
#endif
}

#ifdef __APPLE__
    void IndexThread::addMacOSExcludes(std::vector<std::string>& exclude_dirs)
    {
//        Read the overrides list
        std::ifstream overrides_file("urbackup/macOS_exclusion_overrides.txt");
        if (overrides_file.is_open())
        {
            std::string file_line;
            while (std::getline(overrides_file, file_line))
            {
                const char * file_line_prefix = &file_line[0];
                if (strncmp (file_line_prefix, "#", 1) != 0)
                {
                    IndexThread::macos_overrides.push_back(sanitizePattern(file_line));
                }
            }
            overrides_file.close();
        }

        
//        Exclude these items entirely
        IndexThread::macos_exclusions.push_back("/.MobileBackups*");
        IndexThread::macos_exclusions.push_back("/MobileBackups.trash*");
        IndexThread::macos_exclusions.push_back("/.MobileBackups.trash*");
        IndexThread::macos_exclusions.push_back("/.Spotlight-V100*");
        IndexThread::macos_exclusions.push_back("/.TemporaryItems*");
        IndexThread::macos_exclusions.push_back("/.Trashes*");
        IndexThread::macos_exclusions.push_back("/.com.apple.backupd.mvlist.plist*");
        IndexThread::macos_exclusions.push_back("/.fseventsd*");
        IndexThread::macos_exclusions.push_back("/.hotfiles.btree*");
        IndexThread::macos_exclusions.push_back("/Backups.backupdb*");
        IndexThread::macos_exclusions.push_back("/Desktop DB*");
        IndexThread::macos_exclusions.push_back("/Desktop DF*");
        IndexThread::macos_exclusions.push_back("/Network/Servers*");
        IndexThread::macos_exclusions.push_back("/Library/Updates*");
        IndexThread::macos_exclusions.push_back("/Previous Systems*");
        IndexThread::macos_exclusions.push_back("/Users/Shared/SC Info*");
        IndexThread::macos_exclusions.push_back("/Users/Guest*");
        IndexThread::macos_exclusions.push_back("/dev*");
        IndexThread::macos_exclusions.push_back("/home*");
        IndexThread::macos_exclusions.push_back("/net*");
    IndexThread::macos_exclusions.push_back("/private/var/db/com.apple.backupd.backupVerification*");
        IndexThread::macos_exclusions.push_back("/private/var/db/efw_cache*");
        IndexThread::macos_exclusions.push_back("/private/var/db/Spotlight*");
        IndexThread::macos_exclusions.push_back("/private/var/db/Spotlight-V100*");
        IndexThread::macos_exclusions.push_back("/private/var/db/systemstats*");
        IndexThread::macos_exclusions.push_back("/private/var/lib/postfix/greylist.db*");
        
        IndexThread::macos_exclusions.push_back("/.DocumentRevisions-V100*");
        IndexThread::macos_exclusions.push_back("/.HFS+ Private Directory Data*");
        IndexThread::macos_exclusions.push_back("/private/etc/kcpassword*");
        IndexThread::macos_exclusions.push_back("/private/var/db/dyld*");
        IndexThread::macos_exclusions.push_back("/private/var/db/dyld/shared_region_roots*");
        
//        Backup the top level folder, but exclude all contents
        IndexThread::macos_exclusions.push_back("/Volumes/*");
        IndexThread::macos_exclusions.push_back("/Network/*");
        IndexThread::macos_exclusions.push_back("/automount/*");
        IndexThread::macos_exclusions.push_back("/.vol/*");
        IndexThread::macos_exclusions.push_back("/tmp/*");
        IndexThread::macos_exclusions.push_back("/cores/*");
        IndexThread::macos_exclusions.push_back("/private/tmp/*");
        IndexThread::macos_exclusions.push_back("/private/Network/*");
        IndexThread::macos_exclusions.push_back("/private/tftpboot/*");
        IndexThread::macos_exclusions.push_back("/private/var/automount/*");
        IndexThread::macos_exclusions.push_back("/private/var/folders/*");
        IndexThread::macos_exclusions.push_back("/private/var/run/*");
        IndexThread::macos_exclusions.push_back("/private/var/tmp/*");
        IndexThread::macos_exclusions.push_back("/private/var/vm/*");
        IndexThread::macos_exclusions.push_back("/private/var/db/dhcpclient/*");
        IndexThread::macos_exclusions.push_back("/private/var/db/fseventsd/*");
        IndexThread::macos_exclusions.push_back("/Library/Caches/*");
        IndexThread::macos_exclusions.push_back("/Library/Logs/*");
        IndexThread::macos_exclusions.push_back("/System/Library/Caches/*");
        IndexThread::macos_exclusions.push_back("/System/Library/Extensions/Caches/*");
        
        
//        Backup folder structure, but exclude all files
        IndexThread::macos_exclusions.push_back("/private/var/log/*");
        IndexThread::macos_exclusions.push_back("/private/var/spool/cups/*");
        IndexThread::macos_exclusions.push_back("/private/var/spool/fax/*");
        IndexThread::macos_exclusions.push_back("/private/var/spool/uucp/*");
        
        
//        Exclude these items within each user account
        IndexThread::macos_exclusions.push_back("/Users/:/Library/Application Support/SyncServices/data.version*");
        IndexThread::macos_exclusions.push_back("/Users/:/Library/Application Support/Ubiquity*");
        IndexThread::macos_exclusions.push_back("/Users/:/Library/Caches*");
        IndexThread::macos_exclusions.push_back("/Users/:/Library/Logs*");
        IndexThread::macos_exclusions.push_back("/Users/:/Library/Mail/Envelope Index*");
        IndexThread::macos_exclusions.push_back("/Users/:/Library/Mail/Envelope Index-journal*");
        IndexThread::macos_exclusions.push_back("/Users/:/Library/Mail/AvailableFeeds*");
        IndexThread::macos_exclusions.push_back("/Users/:/Library/Mail/Metadata/BackingStoreUpdateJournal*");
        IndexThread::macos_exclusions.push_back("/Users/:/Library/Mail/V2/MailData/Envelope Index*");
        IndexThread::macos_exclusions.push_back("/Users/:/Library/Mail/V2/MailData/Envelope Index-journal*");
        IndexThread::macos_exclusions.push_back("/Users/:/Library/Mail/V2/MailData/AvailableFeeds*");
        IndexThread::macos_exclusions.push_back("/Users/:/Library/Mail/V2/MailData/BackingStoreUpdateJournal*");
        IndexThread::macos_exclusions.push_back("/Users/:/Library/Mail/V2/MailData/Envelope Index-shm*");
        IndexThread::macos_exclusions.push_back("/Users/:/Library/Mail/V2/MailData/Envelope Index-wal*");
        IndexThread::macos_exclusions.push_back("/Users/:/Library/Mirrors*");
        IndexThread::macos_exclusions.push_back("/Users/:/Library/PubSub/Database*");
        IndexThread::macos_exclusions.push_back("/Users/:/Library/PubSub/Downloads*");
        IndexThread::macos_exclusions.push_back("/Users/:/Library/PubSub/Feeds*");
        IndexThread::macos_exclusions.push_back("/Users/:/Library/Safari/Icons.db*");
        IndexThread::macos_exclusions.push_back("/Users/:/Library/Safari/WebpageIcons.db*");
        IndexThread::macos_exclusions.push_back("/Users/:/Library/Safari/HistoryIndex.sk*");
        IndexThread::macos_exclusions.push_back("/Users/:/.Trash*");
        
        //        Exclude DataVaults
        IndexThread::macos_exclusions.push_back("/Users/:/Library/VoiceTrigger/SAT");
        IndexThread::macos_exclusions.push_back("/Users/:/Library/Containers/com.apple.mail/Data/DataVaults");
        IndexThread::macos_exclusions.push_back("/var/folders/:/:/:/com.apple.nsurlsessiond");
        
//        Exclude volumes from macOS 10.14+
        IndexThread::macos_exclusions.push_back("/System/Volumes/*");

        
//        Filter the standard exclusions with the overrides list
        std::string overridden_macos_exclusions;
        for(size_t i=0; i<IndexThread::macos_exclusions.size(); i++)
        {
            if ( std::find(IndexThread::macos_overrides.begin(), IndexThread::macos_overrides.end(), IndexThread::macos_exclusions[i]) == IndexThread::macos_overrides.end())
            {
                overridden_macos_exclusions = overridden_macos_exclusions + IndexThread::macos_exclusions[i] + ";";
            }
        }
        
        IndexThread::macos_exclusions = parseExcludePatterns(overridden_macos_exclusions);

//        Add the filtered exclusions to the exclude_dirs array
        for(size_t i=0; i<IndexThread::macos_exclusions.size(); i++)
        {
            exclude_dirs.push_back(IndexThread::macos_exclusions[i]);
        }

    }
    

    void IndexThread::logMacOSExclude(const std::string path)
    {
//        Get the exclusion term which caused the file to be skipped, if it was a standard exclusion
        std::string pathMatch = returnExcludeDirsMatch(IndexThread::macos_exclusions, path);
        
//        Log the exclusion
        if(!pathMatch.empty())
        {
            Server->Log("[macOS] Skipping \""+path+"\" due to macOS exclusion \""+pathMatch+"\"", LL_INFO);
        }
    }


//      Based on isExcluded, but returning the exclusion term
    std::string IndexThread::returnExcludeDirsMatch(std::vector<std::string> exclude_dirs, std::string path)
    {
        std::string wpath=path;

        for(size_t i=0;i<exclude_dirs.size();++i)
        {
            if(!exclude_dirs[i].empty())
            {
                bool b=amatch(wpath.c_str(), exclude_dirs[i].c_str());
                if(b)
                {
                    return exclude_dirs[i];
                }
            }
        }
        return std::string();
    }

    bool IndexThread::isExcludedByXattr(const std::string path)
    {
        ssize_t xattr_size = 0;
        char xattr_out[1024];
        std::string backup_excludeItem_string = "com.apple.metadata:com_apple_backup_excludeItem";
        
        xattr_size = listxattr(path.c_str(), NULL, 0,  XATTR_NOFOLLOW);
        listxattr(path.c_str(), xattr_out, 1024,  XATTR_NOFOLLOW);
        if(xattr_size > 0) {
            std::string xattr_str(reinterpret_cast<char*>(xattr_out), xattr_size);
            
            if (xattr_str.find(backup_excludeItem_string) != std::string::npos) {
                //      Based on isExcluded
                std::string wpath=path;

                for(size_t i=0;i<macos_overrides.size();++i)
                {
                    if(!macos_overrides[i].empty())
                    {
                        bool b=amatch(wpath.c_str(), macos_overrides[i].c_str());
                        if(b)
                        {
    //                        File is overridden
                            Server->Log("[macOS] Backing up \""+path+"\" due to overridden macOS extended attribute exclusion", LL_INFO);
                            return false;
                        }
                    }
                }
    //            File is not overridden, and contains backup_excludeItem xattr
                return true;
            }
    //        Does not contain backup_excludeItem xattr
            return false;
        } else {
//            Does not contain any xattr
            return false;
        }
    }

    void IndexThread::logIsExcludedByXattr(const std::string path)
    {
        Server->Log("[macOS] Skipping \""+path+"\" due to macOS extended attribute exclusion", LL_INFO);
    }

#endif


void IndexThread::handleHardLinks(const std::string& bpath, const std::string& vsspath, const std::string& normalized_volume)
{
#ifdef _WIN32
	std::string prefixedbpath=os_file_prefix(bpath);
	std::wstring tvolume;
	tvolume.resize(prefixedbpath.size()+100);
	DWORD cchBufferLength=static_cast<DWORD>(tvolume.size());
	BOOL b=GetVolumePathNameW(Server->ConvertToWchar(prefixedbpath).c_str(), &tvolume[0], cchBufferLength);
	if(!b)
	{
		VSSLog("Error getting volume path for "+bpath, LL_WARNING);
		return;
	}

	std::wstring tvssvolume;
	tvssvolume.resize(vsspath.size()+100);
	cchBufferLength=static_cast<DWORD>(tvssvolume.size());
	b=GetVolumePathNameW(Server->ConvertToWchar(vsspath).c_str(), &tvssvolume[0], cchBufferLength);
	if(!b)
	{
		VSSLog("Error getting volume path for "+vsspath, LL_WARNING);
		return;
	}

	std::string vssvolume=Server->ConvertFromWchar(tvssvolume.c_str());

	std::string volume=strlower(Server->ConvertFromWchar(tvolume.c_str()));

	if(volume.find("\\\\?\\")==0)
		volume.erase(0, 4);

	std::vector<std::string> additional_changed_dirs;
	std::vector<std::string> additional_open_files;

	std::string prev_path;

	for(size_t i=0;i<changed_dirs.size();++i)
	{
		std::string vsstpath;
		{
			std::string tpath=changed_dirs[i];

			if(tpath.find(volume)!=0)
			{
				continue;
			}
			vsstpath=vssvolume+tpath.substr(volume.size());
		}

		if(!prev_path.empty() && prev_path==vsstpath)
		{
			continue;
		}
		else
		{
			prev_path = vsstpath;
		}

		bool has_error;
		std::vector<SFile> files = getFilesWin(os_file_prefix(vsstpath), &has_error, false, false, (index_flags & EBackupDirFlag_OneFilesystem)>0);
		std::vector<std::string> changed_files;
		bool looked_up_changed_files=false;

		if(has_error)
		{
			VSSLog("Cannot open directory "+vsstpath+" to handle hard links", LL_DEBUG);
		}

		const size_t& cdir_idx = i;


		for(size_t i=0;i<files.size();++i)
		{
			if(files[i].isdir)
			{
				continue;
			}

			std::string fn=vsstpath+files[i].name;
			HANDLE hFile = CreateFileW(Server->ConvertToWchar(os_file_prefix(fn)).c_str(), FILE_READ_ATTRIBUTES, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);

			if(hFile==INVALID_HANDLE_VALUE)
			{
				VSSLog("Cannot open file "+fn+" to read the file attributes", LL_INFO);
			}
			else
			{
				FILE_STANDARD_INFO fileInformation;
				BOOL b= GetFileInformationByHandleEx(hFile, FileStandardInfo, &fileInformation, sizeof(fileInformation));

				if(!b)
				{
					VSSLog("Error getting file information of "+fn, LL_INFO);
					CloseHandle(hFile);
				}
				else if(fileInformation.NumberOfLinks>1)
				{
					CloseHandle(hFile);

					uint128 frn = getFrn(fn);


					SHardlinkKey hlkey = { normalized_volume, static_cast<int64>(frn.highPart), static_cast<int64>(frn.lowPart) };
					bool add_hardlinks_db = std::find(modify_hardlink_buffer_keys.begin(),
						modify_hardlink_buffer_keys.end(), hlkey) == modify_hardlink_buffer_keys.end();

					if (frn != uint128()
						&& add_hardlinks_db)
					{
						addResetHardlink(normalized_volume, frn.highPart, frn.lowPart);
					}
					

					bool file_is_open = std::binary_search(open_files.begin(),
						open_files.end(), changed_dirs[cdir_idx]+strlower(files[i].name));

					std::wstring outBuf;
					DWORD stringLength=4096;
					outBuf.resize(stringLength);
					HANDLE hFn=FindFirstFileNameW(Server->ConvertToWchar(os_file_prefix(fn)).c_str(), 0, &stringLength, &outBuf[0]);

					if(hFn==INVALID_HANDLE_VALUE && GetLastError()==ERROR_MORE_DATA)
					{
						outBuf.resize(stringLength);
						hFn=FindFirstFileNameW(Server->ConvertToWchar(os_file_prefix(fn)).c_str(), 0, &stringLength, &outBuf[0]);
					}

					if(hFn==INVALID_HANDLE_VALUE)
					{
						VSSLog("Error reading hard link names of "+fn, LL_INFO);
					}
					else
					{
						std::string nfn = strlower(Server->ConvertFromWchar(std::wstring(outBuf.begin(), outBuf.begin()+stringLength-1)));
						std::string vssnfn;
						if (nfn[0] == '\\')
						{
							vssnfn = vssvolume + nfn.substr(1);
							nfn = volume + nfn.substr(1);
						}
						else
						{
							vssnfn = vssvolume + nfn;
							nfn = volume + nfn;
						}

						std::string ndir=ExtractFilePath(nfn, os_file_sep())+os_file_sep();

						if (add_hardlinks_db)
						{
							uint128 dir_frn = getFrn(ExtractFilePath(vssnfn, os_file_sep()));

							if (dir_frn != uint128()
								&& frn != uint128())
							{
								addHardLink(normalized_volume, frn.highPart, frn.lowPart, dir_frn.highPart, dir_frn.lowPart);
							}
						}
					
						if(!std::binary_search(changed_dirs.begin(), changed_dirs.end(), ndir) )
						{
							additional_changed_dirs.push_back(ndir);
						}

						if(file_is_open &&
							std::find(additional_open_files.begin(), additional_open_files.end(), nfn)==
								additional_open_files.end())
						{
							additional_open_files.push_back(nfn);
						}

						do
						{
							b = FindNextFileNameW(hFn, &stringLength, &outBuf[0]);

							if(!b && GetLastError()==ERROR_MORE_DATA)
							{
								outBuf.resize(stringLength);
								b = FindNextFileNameW(hFn, &stringLength, &outBuf[0]);
							}

							if(!b && GetLastError()!=ERROR_HANDLE_EOF)
							{
								VSSLog("Error reading (2) hard link names of "+fn, LL_INFO);
							}
							else if(b)
							{
								std::string nfn = strlower(std::string(outBuf.begin(), outBuf.begin()+stringLength-1));
								std::string vssnfn;
								if (nfn[0] == '\\')
								{
									vssnfn = vssvolume + nfn.substr(1);
									nfn = volume + nfn.substr(1);
								}
								else
								{
									vssnfn = vssvolume + nfn;
									nfn = volume + nfn;
								}

								std::string ndir=ExtractFilePath(nfn, os_file_sep())+os_file_sep();

								if (add_hardlinks_db)
								{
									uint128 dir_frn = getFrn(ExtractFilePath(vssnfn, os_file_sep()));

									if (dir_frn != uint128()
										&& frn != uint128())
									{
										addHardLink(normalized_volume, frn.highPart, frn.lowPart, dir_frn.highPart, dir_frn.lowPart);
									}
								}
														
								if(!std::binary_search(changed_dirs.begin(), changed_dirs.end(), ndir))
								{
									additional_changed_dirs.push_back(ndir);
								}

								if(file_is_open &&
									std::find(additional_open_files.begin(), additional_open_files.end(), nfn)==
									additional_open_files.end())
								{
									additional_open_files.push_back(nfn);
								}
							}
						}
						while(b);

						FindClose(hFn);
					}
				}
				else
				{
					CloseHandle(hFile);
				}
			}
		}
	}

	if (!additional_changed_dirs.empty())
	{
		changed_dirs.insert(changed_dirs.end(), additional_changed_dirs.begin(), additional_changed_dirs.end());
		std::sort(changed_dirs.begin(), changed_dirs.end());
	}

	if (!additional_open_files.empty())
	{
		open_files.insert(open_files.end(), additional_open_files.begin(), additional_open_files.end());
		std::sort(open_files.begin(), open_files.end());
	}
#endif
}

void IndexThread::enumerateHardLinks(const std::string& volume, const std::string& vssvolume, const std::string & vsspath)
{
#ifdef _WIN32

	uint128 frn = getFrn(vsspath);

	if (frn == uint128())
	{
		return;
	}

	SHardlinkKey hlkey = { volume, static_cast<int64>(frn.highPart), static_cast<int64>(frn.lowPart) };
	if (cd->hasHardLink(volume, frn.highPart, frn.lowPart).exists
		|| std::find(modify_hardlink_buffer_keys.begin(), modify_hardlink_buffer_keys.end(), hlkey)!= modify_hardlink_buffer_keys.end() )
	{
		return;
	}

	std::wstring outBuf;
	DWORD stringLength = 4096;
	outBuf.resize(stringLength);
	HANDLE hFn = FindFirstFileNameW(Server->ConvertToWchar(os_file_prefix(vsspath)).c_str(), 0, &stringLength, &outBuf[0]);

	if (hFn == INVALID_HANDLE_VALUE && GetLastError() == ERROR_MORE_DATA)
	{
		outBuf.resize(stringLength);
		hFn = FindFirstFileNameW(Server->ConvertToWchar(os_file_prefix(vsspath)).c_str(), 0, &stringLength, &outBuf[0]);
	}

	if (hFn == INVALID_HANDLE_VALUE)
	{
		VSSLog("Error reading hard link names of " + vsspath+". "+os_last_error_str(), LL_ERROR);
	}
	else
	{
		std::string nfn = strlower(Server->ConvertFromWchar(std::wstring(outBuf.begin(), outBuf.begin() + stringLength - 1)));
		if (nfn[0] == '\\')
			nfn = vssvolume + nfn;
		else
			nfn = vssvolume + os_file_sep() + nfn;

		std::string ndir = ExtractFilePath(nfn, os_file_sep());

		uint128 dir_frn = getFrn(ndir);

		if (dir_frn != uint128())
		{
			addHardLink(volume, frn.highPart, frn.lowPart, dir_frn.highPart, dir_frn.lowPart);
		}

		BOOL b;

		do
		{
			b = FindNextFileNameW(hFn, &stringLength, &outBuf[0]);

			if (!b && GetLastError() == ERROR_MORE_DATA)
			{
				outBuf.resize(stringLength);
				b = FindNextFileNameW(hFn, &stringLength, &outBuf[0]);
			}

			if (!b && GetLastError() != ERROR_HANDLE_EOF)
			{
				VSSLog("Error reading (2) hard link names of " + vsspath +". "+os_last_error_str(), LL_INFO);
			}
			else if (b)
			{
				std::string nfn = strlower(std::string(outBuf.begin(), outBuf.begin() + stringLength - 1));
				if (nfn[0] == '\\')
					nfn = vssvolume + nfn;
				else
					nfn = vssvolume + os_file_sep() + nfn;

				std::string ndir = ExtractFilePath(nfn, os_file_sep());

				dir_frn = getFrn(ndir);

				if (dir_frn != uint128())
				{
					addHardLink(volume, frn.highPart, frn.lowPart, dir_frn.highPart, dir_frn.lowPart);
				}
			}
		} while (b);

		FindClose(hFn);
	}
#endif //_WIN32
}

void IndexThread::addResetHardlink(const std::string & volume, int64 frn_high, int64 frn_low)
{
	if (modify_hardlink_buffer_keys.size() > 1000
		|| modify_hardlink_buffer.size() > 10000)
	{
		commitModifyHardLinks();
	}

	SHardlinkKey key = { volume, frn_high, frn_low };
	modify_hardlink_buffer_keys.push_back(key);
}

void IndexThread::addHardLink(const std::string & volume, int64 frn_high, int64 frn_low, int64 parent_frn_high, int64 parent_frn_low)
{
	SHardlinkKey key = { volume, frn_high, frn_low };
	SHardlink hl = { key, parent_frn_high, parent_frn_low };
	modify_hardlink_buffer.push_back(hl);
}

void IndexThread::commitModifyHardLinks()
{
	DBScopedWriteTransaction transaction(db);

	for (size_t i = 0; i < modify_hardlink_buffer_keys.size(); ++i)
	{
		cd->resetHardlink(modify_hardlink_buffer_keys[i].volume, modify_hardlink_buffer_keys[i].frn_high, modify_hardlink_buffer_keys[i].frn_low);
	}

	modify_hardlink_buffer_keys.clear();

	for (size_t i = 0; i < modify_hardlink_buffer.size(); ++i)
	{
		cd->addHardlink(modify_hardlink_buffer[i].key.volume, modify_hardlink_buffer[i].key.frn_high, modify_hardlink_buffer[i].key.frn_low,
			modify_hardlink_buffer[i].parent_frn_high, modify_hardlink_buffer[i].parent_frn_low);
	}

	modify_hardlink_buffer.clear();
}

#ifdef _WIN32
uint128 IndexThread::getFrn(const std::string & fn)
{
	HANDLE hFile = CreateFileW(Server->ConvertToWchar(os_file_prefix(fn)).c_str(), FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS| FILE_FLAG_OPEN_REPARSE_POINT, NULL);

	if (hFile == INVALID_HANDLE_VALUE)
	{
		VSSLog("Cannot open file " + fn + " to read the FRN. "+os_last_error_str(), LL_ERROR);
		return uint128();
	}
	else
	{
		BY_HANDLE_FILE_INFORMATION fileInformation;
		BOOL b = GetFileInformationByHandle(hFile, &fileInformation);

		CloseHandle(hFile);

		if (b)
		{
			LARGE_INTEGER frn;
			frn.LowPart = fileInformation.nFileIndexLow;
			frn.HighPart = fileInformation.nFileIndexHigh;

			//TODO: Handle ReFS 128-bit FRN
			return uint128(frn.QuadPart);
		}
		else
		{
			VSSLog("Cannot get FRN of " + fn +". "+os_last_error_str(), LL_ERROR);
			return uint128();
		}
	}
}
#endif

std::string IndexThread::escapeListName( const std::string& listname )
{
	std::string ret;
	ret.reserve(listname.size());
	for(size_t i=0;i<listname.size();++i)
	{
		if(listname[i]=='"')
		{
			ret+="\\\"";
		}
		else if(listname[i]=='\\')
		{
			ret+="\\\\";
		}
		else
		{
			ret+=listname[i];
		}
	}
	return ret;
}

std::string IndexThread::getShaBinary(const std::string& fn)
{
	VSSLog("Hashing file \"" + fn + "\"", LL_DEBUG);

	if (sha_version == 256)
	{
		HashSha256 hash_256;
		if (!getShaBinary(fn, hash_256, false))
		{
			return std::string();
		}

		return hash_256.finalize();
	}
	else if (sha_version == 528)
	{
		TreeHash treehash(index_hdat_file.get() == nullptr ? nullptr : client_hash.get());
		if (!getShaBinary(fn, treehash, index_hdat_file.get() != nullptr))
		{
			return std::string();
		}

		return treehash.finalize();
	}
	else
	{
		HashSha512 hash_512;
		if (!getShaBinary(fn, hash_512, false))
		{
			return std::string();
		}

		return hash_512.finalize();
	}
}

bool IndexThread::getShaBinary( const std::string& fn, IHashFunc& hf, bool with_cbt)
{
	return client_hash->getShaBinary(fn, hf, with_cbt);
}

bool IndexThread::backgroundBackupsEnabled(const std::string& clientsubname)
{
	std::string settings_fn = "urbackup/data/settings.cfg";

	if(!clientsubname.empty())
	{
		settings_fn = "urbackup/data/settings_"+conv_filename(clientsubname)+".cfg";
	}

	std::unique_ptr<ISettingsReader> curr_settings(Server->createFileSettingsReader(settings_fn));
	if(curr_settings.get()!=nullptr)
	{
		std::string background_backups;
		if(curr_settings->getValue("background_backups", &background_backups)
			|| curr_settings->getValue("background_backups_def", &background_backups) )
		{
			return background_backups!="false";
		}
	}
	return true;
}

bool IndexThread::pauseIfWindowsUnlocked()
{
	std::string settings_fn = "urbackup/data/settings.cfg";

	std::unique_ptr<ISettingsReader> curr_settings(Server->createFileSettingsReader(settings_fn));
	if (curr_settings.get() != nullptr)
	{
		std::string pause_if_windows_unlocked;
		if (curr_settings->getValue("pause_if_windows_unlocked", &pause_if_windows_unlocked)
			|| curr_settings->getValue("pause_if_windows_unlocked_def", &pause_if_windows_unlocked))
		{
			return pause_if_windows_unlocked == "true";
		}
	}
	return false;
}

void IndexThread::writeTokens()
{
	std::unique_ptr<ISettingsReader> access_keys(
		Server->createFileSettingsReader("urbackup/access_keys.properties"));

	std::string access_keys_data;
	std::vector<std::string> keys
		= access_keys->getKeys();


	bool has_server_key=false;
	std::string curr_key;
	int64 curr_key_age = 0;
	for(size_t i=0;i<keys.size();++i)
	{
		if(keys[i]=="key."+starttoken)
		{
			has_server_key=true;
			curr_key=access_keys->getValue(keys[i], std::string());
			curr_key_age = access_keys->getValue("key_age."+starttoken, Server->getTimeSeconds());
		}
		else if(keys[i]!="key."+starttoken
			&& keys[i]!="last.key."+starttoken)
		{
			access_keys_data+=keys[i]+"="+
				access_keys->getValue(keys[i], std::string())+"\n";
		}
	}

	bool modified_file = false;
	std::string last_key;

	if(!has_server_key || (Server->getTimeSeconds()-curr_key_age)>7*24*60*60)
	{
		if (has_server_key)
		{
			last_key = curr_key;
		}
		curr_key = Server->secureRandomString(32);
		curr_key_age = Server->getTimeSeconds();
		modified_file=true;
	}

	if (!last_key.empty())
	{
		access_keys_data += "last.key." + starttoken + "=" +
			last_key + "\n";
	}

	access_keys_data+="key."+starttoken+"="+
		curr_key+"\n";

	access_keys_data+="key_age."+starttoken+"="+
		convert(curr_key_age)+"\n";

	if(modified_file)
	{
		write_file_only_admin(access_keys_data, "urbackup/access_keys.properties.new");
		os_rename_file("urbackup/access_keys.properties.new", "urbackup/access_keys.properties");
	}	

	tokens::write_tokens();
	std::vector<ClientDAO::SToken> tokens = cd->getFileAccessTokens();

	std::string ids;
	std::string uids;
	std::string real_uids;
	for(size_t i=0;i<tokens.size();++i)
	{
		if(!ids.empty())
		{
			ids+=",";
		}
		ids+=convert(tokens[i].id);

		if(tokens[i].is_user)
		{
			if(!uids.empty())
			{
				uids+=",";
			}
			uids+=convert(tokens[i].id);
		}

		if(tokens[i].is_user &&
				tokens[i].is_user!=ClientDAO::c_is_system_user)
		{
			if(!real_uids.empty())
			{
				real_uids+=",";
			}
			real_uids+=convert(tokens[i].id);
		}

	}

	std::string data="ids="+ids+"\n";
	data+="access_key="+curr_key+"\n";
	data+="uids="+uids+"\n";
	data+="real_uids="+real_uids+"\n";

	for(size_t i=0;i<tokens.size();++i)
	{
		data+=convert(tokens[i].id)+".accountname="+base64_encode_dash((tokens[i].accountname))+"\n";
		data+=convert(tokens[i].id)+".token="+(tokens[i].token)+"\n";

		if(tokens[i].is_user)
		{
			std::vector<int> groups = cd->getGroupMembership(static_cast<int>(tokens[i].id));

			std::string gids;

			for(size_t j=0;j<groups.size();++j)
			{
				if(!gids.empty())
				{
					gids+=",";
				}
				gids+=convert(groups[j]);
			}

			data+=convert(tokens[i].id)+".gids="+gids+"\n";
		}
	}


	write_file_only_admin(data, "urbackup"+os_file_sep()+"data"+os_file_sep()+"tokens_"+starttoken+".properties");
}

void IndexThread::writeDir(std::fstream& out, const std::string& name, bool with_change, uint64 change_identicator, const std::string& extra)
{
	out << "d\"" << escapeListName((name)) << "\"";

	if(with_change)
	{
		out << " 0 " << static_cast<int64>(change_identicator);
	}

	if(!extra.empty())
	{
		if(extra[0]=='&')
			out << "#" << extra.substr(1) << "\n";
		else
			out << extra << "\n";
	}
	else
	{
		out << "\n";
	}

	++file_id;;
}

std::string IndexThread::execute_script(const std::string& cmd, const std::string& args)
{
	std::string output;
	int rc = os_popen("\""+cmd+"\"" + (args.empty() ? "" : (" "+args)), output);

	if(rc!=0)
	{
		Server->Log("Script "+cmd+" had error (code "+convert(rc)+"). Not using this script list.", LL_ERROR);
		return std::string();
	}

	return output;
}

int IndexThread::execute_preimagebackup_hook(bool incr, std::string server_token)
{
	std::string script_name;
#ifdef _WIN32
	script_name = Server->getServerWorkingDir() + "\\preimagebackup.bat";
#else
	script_name = SYSCONFDIR "/urbackup/preimagebackup";
#endif

	return execute_hook(script_name, incr, server_token, nullptr);
}

bool IndexThread::addBackupScripts(std::fstream& outfile)
{
	if(!scripts.empty())
	{
		outfile << "d\"urbackup_backup_scripts\"\n";
		++file_id;

		for(size_t i=0;i<scripts.size();++i)
		{
			int64 rndnum;
			if (scripts[i].lastmod != -1)
				rndnum = scripts[i].lastmod;
			else
				rndnum = Server->getRandomNumber() << 30 | Server->getRandomNumber();

			outfile << "f\"" << escapeListName(scripts[i].outputname) << "\" " << scripts[i].size << " " << rndnum;
			++file_id;

			if (!scripts[i].orig_path.empty())
			{
				std::string orig_path = scripts[i].orig_path;
				if (orig_path.size() > 1 && orig_path[orig_path.size() - 1] == os_file_sep()[0])
				{
					orig_path.erase(orig_path.size() - 1, 1);
				}
				outfile << "#orig_path=" << EscapeParamString(orig_path) << "&orig_sep=" << EscapeParamString(os_file_sep());
			}
			
			outfile << "\n";
		}

		outfile << "u\n";
		++file_id;

		return true;
	}
	else
	{
		return false;
	}
}

void IndexThread::monitor_disk_failures()
{
#ifdef _WIN32
	std::vector<SFailedDisk> failed_disks = get_failed_disks();

	for (size_t i = 0; i < failed_disks.size(); ++i)
	{
		VSSLog("Disk \"" + failed_disks[i].name + "\" has status \"" + failed_disks[i].status + "\" and may need replacement" +
			(failed_disks[i].status_info.empty() ? "" : "(Further info: \"" + failed_disks[i].status_info + "\")"), LL_WARNING);
	}
#endif
}

int IndexThread::get_db_tgroup()
{
	if (index_flags & EBackupDirFlag_ShareHashes)
	{
		return 0;
	}
	else
	{
		return index_group + 1;
	}
}

bool IndexThread::nextLastFilelistItem(SFile& data, str_map* extra, bool with_up)
{
	if (last_filelist.get() == nullptr
		|| last_filelist->f==nullptr)
	{
		return false;
	}

	if (last_filelist->buf.empty())
	{
		last_filelist->buf.resize(4096);
		last_filelist->buf_pos = std::string::npos;
	}

	while (true)
	{
		if (last_filelist->buf_pos == last_filelist->buf.size()
			|| last_filelist->buf_pos==std::string::npos)
		{
			if (last_filelist->buf_pos == last_filelist->buf.size())
			{
				last_filelist->read_pos += last_filelist->buf.size();
			}

			last_filelist->buf_pos = 0;
			bool has_read_error = false;
			_u32 read = last_filelist->f->Read(last_filelist->buf.data(),
				static_cast<_u32>(last_filelist->buf.size()), &has_read_error);
			if (has_read_error)
			{
				Server->Log("Error reading from last file list", LL_ERROR);

				last_filelist.reset();
				index_follow_last = false;
				return false;
			}

			if (read == 0)
			{
				last_filelist.reset();
				index_follow_last = false;
				return false;
			}

			if (read < last_filelist->buf.size())
			{
				last_filelist->buf.resize(read);
			}
		}
		else
		{
			if (last_filelist->parser.nextEntry(last_filelist->buf[last_filelist->buf_pos++], data, extra))
			{
				handleLastFilelistDepth(data);
				last_filelist->item_pos = last_filelist->read_pos + last_filelist->buf_pos;

				if (!with_up && data.isdir && data.name == "..")
				{
					return nextLastFilelistItem(data, extra, with_up);
				}

				return true;
			}
		}
	}
}

void IndexThread::addFromLastUpto(const std::string& fname, bool isdir, size_t depth, bool finish, std::fstream &outfile)
{
	if (!index_follow_last || last_filelist.get()==nullptr)
	{
		return;
	}

	if (last_filelist->item.name.empty())
	{
		if (!nextLastFilelistItem(last_filelist->item, &last_filelist->extra, false))
		{
			return;
		}
	}

	assert(depth >= last_filelist->depth);
	
	do
	{
		if (!finish
			&& ((last_filelist->item.name > fname
				&& last_filelist->item.isdir == isdir
				&& depth == last_filelist->depth)
				|| depth > last_filelist->depth))
		{
			return;
		}

		if (!finish
			&& last_filelist->item.name == fname
			&& last_filelist->item.isdir == isdir
			&& depth == last_filelist->depth)
		{
			nextLastFilelistItem(last_filelist->item, &last_filelist->extra, false);
			return;
		}

		if (index_keep_files)
		{
			if (last_filelist->item.isdir)
			{
				addDirFromLast(outfile);
			}
			else
			{
				addFileFromLast(outfile);
			}
		}
	} while (nextLastFilelistItem(last_filelist->item, &last_filelist->extra, false));
}

void IndexThread::addFromLastLiftDepth(size_t depth, std::fstream & outfile)
{
	if (!index_follow_last || last_filelist.get() == nullptr)
	{
		return;
	}

	if (last_filelist->item.name.empty())
	{
		if (!nextLastFilelistItem(last_filelist->item, &last_filelist->extra, false))
		{
			return;
		}
	}

	while (last_filelist->depth > depth)
	{
		if (index_keep_files)
		{
			if (last_filelist->item.isdir)
			{
				addDirFromLast(outfile);
			}
			else
			{
				addFileFromLast(outfile);
			}
		}

		if (!nextLastFilelistItem(last_filelist->item, &last_filelist->extra, false))
		{
			return;
		}
	}
}

void IndexThread::addDirFromLast(std::fstream & outfile)
{
	size_t curr_depth = last_filelist->depth;
	do
	{
		if (last_filelist->item.isdir)
		{
			std::string str_extra;
			for (str_map::iterator it = last_filelist->extra.begin(); it != last_filelist->extra.end(); ++it)
			{
				str_extra += "&" + it->first + "=" + EscapeParamString(it->second);
			}

			writeDir(outfile, last_filelist->item.name, with_orig_path, last_filelist->item.last_modified, str_extra);
		}
		else
		{
			addFileFromLast(outfile);
		}		
	} while (last_filelist->depth_next > curr_depth
		&& nextLastFilelistItem(last_filelist->item, &last_filelist->extra, true));
}

void IndexThread::addFileFromLast(std::fstream & outfile)
{
	std::string str_extra;
	for (str_map::iterator it = last_filelist->extra.begin(); it != last_filelist->extra.end(); ++it)
	{
		str_extra += "&" + it->first + "=" + EscapeParamString(it->second);
	}

	outfile << "f\"" << escapeListName(last_filelist->item.name) << "\" " << last_filelist->item.size << " " << last_filelist->item.last_modified;

	if (!str_extra.empty())
	{
		str_extra[0] = '#';
		outfile << str_extra;
	}

	outfile << "\n";
	++file_id;
}

bool IndexThread::handleLastFilelistDepth(SFile& data)
{
	last_filelist->depth = last_filelist->depth_next;

	if (data.isdir)
	{
		if (data.name == "..")
		{
			if (last_filelist->depth_next == 0)
			{
				return false;
			}

			--last_filelist->depth_next;
		}
		else
		{
			++last_filelist->depth_next;
		}
	}

	return true;
}

bool IndexThread::volIsEnabled(std::string settings_val, std::string volume)
{
	settings_val = trim(settings_val);

	if (strlower(settings_val) == "all")
	{
		return true;
	}

	volume = trim(volume);

	if (!normalizeVolume(volume))
		return true;

	volume = strlower(volume);

	std::vector<std::string> vols;
	Tokenize(settings_val, vols, ",;");

	for (size_t i = 0; i < vols.size(); ++i)
	{
		std::string cvol = trim(vols[i]);
		if (!normalizeVolume(cvol))
			continue;
		cvol = strlower(cvol);

		if (cvol == volume)
		{
			return true;
		}
	}

	return false;
}

bool IndexThread::cbtIsEnabled(std::string clientsubname, std::string volume)
{
	return false;

	std::string settings_fn = "urbackup/data_"+convert(index_facet_id)+"/settings.cfg";

	if (!clientsubname.empty())
	{
		settings_fn = "urbackup/data_"+ convert(index_facet_id) + "/settings_" + conv_filename(clientsubname) + ".cfg";
	}

	std::unique_ptr<ISettingsReader> curr_settings(Server->createFileSettingsReader(settings_fn));
	if (curr_settings.get() != nullptr)
	{
		std::string cbt_volumes;
		if (curr_settings->getValue("cbt_volumes", &cbt_volumes)
			|| curr_settings->getValue("cbt_volumes_def", &cbt_volumes))
		{
			return volIsEnabled(cbt_volumes, volume);
		}
	}
	return true;
}

bool IndexThread::crashPersistentCbtIsEnabled(std::string clientsubname, std::string volume)
{
	std::string settings_fn = "urbackup/data/settings.cfg";

	if (!clientsubname.empty())
	{
		settings_fn = "urbackup/data/settings_" + conv_filename(clientsubname) + ".cfg";
	}

	std::unique_ptr<ISettingsReader> curr_settings(Server->createFileSettingsReader(settings_fn));
	if (curr_settings.get() != nullptr)
	{
		std::string cbt_crash_persistent_volumes;
		if (curr_settings->getValue("cbt_crash_persistent_volumes", &cbt_crash_persistent_volumes)
			|| curr_settings->getValue("cbt_crash_persistent_volumes_def", &cbt_crash_persistent_volumes))
		{
			return volIsEnabled(cbt_crash_persistent_volumes, volume);
		}
	}
	return false;
}

#ifdef _WIN32
#define URBT_BLOCKSIZE (512 * 1024)
#define URBT_MAGIC "~urbackupcbt!"
#define URBT_MAGIC_SIZE 13

typedef struct _URBCT_BITMAP_DATA
{
	DWORD BitmapSize;
	ULONG SectorSize;
	BYTE Bitmap[1];
} URBCT_BITMAP_DATA, *PURBCT_BITMAP_DATA;

#define IOCTL_URBCT_RESET_START CTL_CODE(FILE_DEVICE_DISK, 3240, METHOD_BUFFERED, FILE_READ_DATA)
#define IOCTL_URBCT_RETRIEVE_BITMAP CTL_CODE(FILE_DEVICE_DISK, 3241, METHOD_BUFFERED, FILE_READ_DATA)
#define IOCTL_URBCT_RESET_FINISH CTL_CODE(FILE_DEVICE_DISK, 3242, METHOD_BUFFERED, FILE_READ_DATA)
#define IOCTL_URBCT_MARK_ALL CTL_CODE(FILE_DEVICE_DISK, 3245, METHOD_BUFFERED, FILE_READ_DATA)
#define IOCTL_URBCT_APPLY_BITMAP CTL_CODE(FILE_DEVICE_DISK, 3246, METHOD_BUFFERED, FILE_READ_DATA)

namespace
{
	class ScopedCloseWindowsHandle
	{
	public:
		ScopedCloseWindowsHandle(HANDLE h)
			: h(h)
		{}

		~ScopedCloseWindowsHandle() {
			if (h != INVALID_HANDLE_VALUE) {
				CloseHandle(h);
			}
		}
	private:
		HANDLE h;
	};

	PURBCT_BITMAP_DATA readBitmap(std::string fn, std::vector<char>& data)
	{
		std::unique_ptr<IFile> f(Server->openFile(fn, MODE_READ));

		if (f.get() == NULL)
		{
			Server->Log("Error opening file " + fn + ". " + os_last_error_str(), LL_ERROR);
			return NULL;
		}

		if (f->Size() < 16)
		{
			return NULL;
		}

		std::string md5sum = f->Read(16);

		if (md5sum.size() != 16)
		{
			return NULL;
		}

		data.resize(f->Size()-16);

		size_t read = 0;
		while (read < data.size())
		{
			bool has_read_error = false;
			_u32 radd = f->Read(data.data() + read, static_cast<_u32>(data.size() - read), &has_read_error);

			read += radd;

			if (has_read_error || radd==0)
			{
				Server->Log("Error reading from file " + fn + ". " + os_last_error_str(), LL_ERROR);
				return NULL;
			}
		}

		MD5 md;
		md.update(reinterpret_cast<unsigned char*>(data.data()), static_cast<_u32>(data.size()));
		md.finalize();

		if (memcmp(md5sum.data(), md.raw_digest_int(), md5sum.size()) != 0)
		{
			Server->Log("Checksum of " + fn + " wrong", LL_ERROR);
			return NULL;
		}

		return reinterpret_cast<PURBCT_BITMAP_DATA>(data.data());
	}

	bool mergeBitmap(PURBCT_BITMAP_DATA src, PURBCT_BITMAP_DATA dst)
	{
		DWORD curr_byte = 0;
		for (DWORD i = 0, j=0; i < src->BitmapSize && j<dst->BitmapSize;)
		{
			if (i%src->SectorSize == 0)
			{
				i += URBT_MAGIC_SIZE;
				continue;
			}

			if (j%dst->SectorSize == 0)
			{
				j += URBT_MAGIC_SIZE;
				continue;
			}

			dst->Bitmap[j] |= src->Bitmap[i];
			++i;
			++j;
		}

		return true;
	}

	bool readMergeBitmap(std::string fn, PURBCT_BITMAP_DATA bitmap)
	{
		if (!FileExists(fn))
		{
			Server->Log("Bitmap " + fn + " does not exist. Nothing to merge.", LL_DEBUG);
			return true;
		}

		PURBCT_BITMAP_DATA old_bitmap;
		std::vector<char> old_bitmap_data;

		old_bitmap = readBitmap(fn, old_bitmap_data);

		if (old_bitmap == NULL)
		{
			return false;
		}

		return mergeBitmap(old_bitmap, bitmap);
	}

	bool saveMergeBitmap(std::string fn, PURBCT_BITMAP_DATA bitmap)
	{
		PURBCT_BITMAP_DATA old_bitmap=NULL;
		std::vector<char> old_bitmap_data;

		if (FileExists(fn))
		{
			old_bitmap = readBitmap(fn, old_bitmap_data);

			if (old_bitmap == NULL)
			{
				return false;
			}
		}

		std::unique_ptr<IFile> f(Server->openFile(fn+".new", MODE_WRITE));

		if (f.get() == NULL)
		{
			Server->Log("Error creating file " + fn + ".new. " + os_last_error_str(), LL_ERROR);
			return false;
		}

		if (old_bitmap != NULL)
		{
			if (!mergeBitmap(bitmap, old_bitmap))
			{
				return false;
			}
		}
		else
		{
			old_bitmap = bitmap;
		}

		size_t dsize = old_bitmap->BitmapSize + sizeof(DWORD) + sizeof(ULONG);

		MD5 md;
		md.update(reinterpret_cast<unsigned char*>(old_bitmap), static_cast<_u32>(dsize));
		md.finalize();

		if (f->Write(reinterpret_cast<char*>(md.raw_digest_int()), 16) != 16)
		{
			Server->Log("Error writing bitmap checksum. " + os_last_error_str(), LL_ERROR);
			return false;
		}

		if (f->Write(reinterpret_cast<char*>(old_bitmap), static_cast<_u32>(dsize)) != dsize)
		{
			Server->Log("Error writing bitmap. " + os_last_error_str(), LL_ERROR);
			return false;
		}

		f->Sync();
		f.reset();

		if (!os_rename_file(fn + ".new", fn))
		{
			Server->Log("Error renaming " + fn + ".new to " + fn + ". " + os_last_error_str(), LL_ERROR);
			return false;
		}

		f.reset(Server->openFile(fn, MODE_RW));

		if (f.get() == NULL)
		{
			return false;
		}
		
		f->Sync();

		return true;
	}
}

#endif

bool IndexThread::prepareCbt(std::string volume)
{
#ifdef _WIN32
	if (!normalizeVolume(volume))
	{
		VSSLog("Error normalizing volume. Input \"" + volume + "\" (1)", LL_ERROR);
		return false;
	}

	if (!cbtIsEnabled(std::string(), volume))
	{
		VSSLog("CBT not enabled for volume \"" + volume + "\"", LL_INFO);
		return false;
	}

	HANDLE hVolume = CreateFileA(("\\\\.\\" + volume).c_str(), GENERIC_READ,
		FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING,
		FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS, NULL);

	if (hVolume == INVALID_HANDLE_VALUE)
	{
		VSSLog("Error opening volume " + volume + ". " + os_last_error_str(), LL_ERROR);
		return false;
	}

	lock_cbt_mutex();

	ScopedCloseWindowsHandle hclose(hVolume);

	DWORD bytesReturned;
	BOOL b = DeviceIoControl(hVolume, IOCTL_URBCT_RESET_START, NULL, 0, NULL, 0, &bytesReturned, NULL);

	if (!b)
	{
		DWORD lasterr = GetLastError();
		std::string errmsg;
		int64 err = os_last_error(errmsg);

		unlock_cbt_mutex();

		int ll = (lasterr != ERROR_INVALID_FUNCTION 
			&& lasterr!= ERROR_NOT_SUPPORTED ) ? LL_ERROR : LL_DEBUG;
		VSSLog("Preparing change block tracking reset for volume " + volume + " failed: " + errmsg + " (code: " + convert(err) + ")", ll);
		
		if ( ((lasterr == ERROR_INVALID_FUNCTION
			|| lasterr == ERROR_NOT_SUPPORTED
			|| lasterr == ERROR_ALLOCATE_BUCKET )
				&& os_get_file_type("urbctctl.exe")!=0 )
			|| (lasterr !=ERROR_INVALID_FUNCTION 
				&& lasterr!= ERROR_NOT_SUPPORTED) )
		{
			if (cbtIsEnabled(std::string(), volume))
			{
				enableCbtVol(volume, true, lasterr==ERROR_ALLOCATE_BUCKET);
			}
		}
	}

	return b == TRUE;
#else
	return false;
#endif
}

bool IndexThread::normalizeVolume(std::string & volume)
{
#ifdef _WIN32
	if (volume.empty())
	{
		return false;
	}

	if (volume[volume.size() - 1] == os_file_sep()[0])
	{
		volume = volume.substr(0, volume.size() - 1);
	}

	if (volume.size() > 2)
	{
		volume = getVolPath(volume);

		if (volume.empty())
		{
			return false;
		}
	}
	else if (volume.size() == 1)
	{
		volume += ":";
	}

	if (!volume.empty()
		&& volume[volume.size() - 1] == os_file_sep()[0])
	{
		volume = volume.substr(0, volume.size() - 1);
	}
#endif

	return true;
}

#ifndef _WIN32
namespace
{
#define DATTO_UUID_SIZE 16

	struct dattobd_info
	{
		unsigned int minor;
		unsigned long state;
		int error;
		unsigned long cache_size;
		unsigned long long falloc_size;
		unsigned long long seqid;
		char uuid[DATTO_UUID_SIZE];
		char cow[PATH_MAX];
		char bdev[PATH_MAX];
		unsigned long long version;
		unsigned long long nr_changed_blocks;
	};

	struct dattobd_header
	{
		unsigned int magic;
		unsigned int flags;
		uint64 fpos;
		uint64 fsize;
		uint64 seqid;
		char uuid[DATTO_UUID_SIZE];
		uint64 version;
		uint64 nr_changed_blocks;
	};

#define IOCTL_DATTOBD_INFO _IOR(DATTO_IOCTL_MAGIC, 8, struct dattobd_info)
#define DATTO_MAGIC ((unsigned int)4776)
#define DATTO_IOCTL_MAGIC 0x91
}
#endif

#ifdef _WIN32
inline int64 roundDown(int64 numToRound, int64 multiple)
{
	return ((numToRound / multiple) * multiple);
}

bool  IndexThread::addFileToCbt(const std::string& fpath, const DWORD& blocksize, const PURBCT_BITMAP_DATA& bitmap_data)
{
	std::unique_ptr<IFsFile> hive_file(Server->openFile(fpath, MODE_READ));

	if (hive_file.get() != nullptr)
	{
		bool ret = true;
		bool more_exts = true;
		int64 offset = 0;
		while (more_exts)
		{
			std::vector<IFsFile::SFileExtent> exts = hive_file->getFileExtents(offset, blocksize, more_exts);
			for (size_t j = 0; j < exts.size(); ++j)
			{
				offset = (std::max)(offset, exts[j].offset + exts[j].size);

				if (exts[j].volume_offset < 0)
					continue;

				for (int64 k = roundDown(exts[j].volume_offset, URBT_BLOCKSIZE);
					k < exts[j].volume_offset + exts[j].size; k += URBT_BLOCKSIZE)
				{
					int64 block = k / URBT_BLOCKSIZE;
					int64 bitmap_byte_wmagic = block / 8;
					int64 real_block_size = bitmap_data->SectorSize - URBT_MAGIC_SIZE;
					int64 bitmap_byte = (bitmap_byte_wmagic / real_block_size)
						* bitmap_data->SectorSize + URBT_MAGIC_SIZE + bitmap_byte_wmagic % real_block_size;
					unsigned int bitmap_bit = block % 8;

					if (bitmap_byte >= bitmap_data->BitmapSize)
					{
						VSSLog("Registry hive extent outside of cbt data", LL_WARNING);
						ret = false;
					}
					else
					{
						bitmap_data->Bitmap[bitmap_byte] |= (1 << bitmap_bit);
					}
				}
			}
		}
		return ret;
	}
	else
	{
		return false;
	}
}
#endif

bool  IndexThread::punchHoleOrZero(IFile* f, int64 pos, const char* zero_buf, char* zero_read_buf, size_t zero_size)
{
	if (!f->PunchHole(pos, zero_size))
	{
		bool data_differs = (f->Read(pos, zero_read_buf, static_cast<_u32>(zero_size)) != zero_size
			|| memcmp(zero_read_buf, zero_buf, static_cast<_u32>(zero_size)) != 0);

		if (data_differs &&
			f->Write(pos, zero_buf, static_cast<_u32>(zero_size)) != zero_size)
		{
			VSSLog("Errro zeroing file hash data in "+f->getFilename()+". " + os_last_error_str(), LL_ERROR);
			return false;
		}
	}

	return true;
}

void IndexThread::run_sc_refs_cleanup()
{
	bool has_cleanup = false;
	bool retry_all = true;
	while(retry_all)
	{
		retry_all = false;
		for (size_t i = 0; i < sc_refs.size(); ++i)
		{
			if (sc_refs[i]->cleanup)
			{
				has_cleanup = true;

				bool in_use = false;

				bool found_ref = false;
				
				starttoken.clear();

				SCRef* curr = sc_refs[i];

				for (size_t k = 0; k < curr->starttokens.size(); ++k)
				{
					starttoken = curr->starttokens[k];

					for (std::map<SCDirServerKey, std::map<std::string, SCDirs*> >::iterator it_scdirs = scdirs.begin();
						it_scdirs != scdirs.end(); ++it_scdirs)
					{
						std::map<std::string, SCDirs*>& scdirs_server = it_scdirs->second;

						VSS_ID ssetid = curr->ssetid;

						bool in_use = false;
						std::vector<std::string> paths;
						for (std::map<std::string, SCDirs*>::iterator it = scdirs_server.begin();
							it != scdirs_server.end(); ++it)
						{
							if (filesrv != nullptr
								&& filesrv->hasActiveTransfersGen(it->second->dir, it_scdirs->first.start_token, curr->cleanup_gen))
							{
								VSSLog(it->first + " orig_target=" + it->second->orig_target + " target=" + it->second->target + " gen="+convert(curr->cleanup_gen)+" still in use. Not releasing.", LL_DEBUG);
								in_use = true;
								break;
							}

							paths.push_back(it->first);
						}

						if (in_use)
							break;

						for (size_t j = 0; j < paths.size(); ++j)
						{
							std::map<std::string, SCDirs*>::iterator it = scdirs_server.find(paths[j]);
							if (it != scdirs_server.end()
								&& it->second->ref == curr)
							{
								VSSLog("Releasing " + it->first + " orig_target=" + it->second->orig_target + " target=" + it->second->target + " (run_sc_refs_cleanup)", LL_DEBUG);
								found_ref = true;
								release_shadowcopy(it->second, false, -1);
							}
						}

						bool retry = true;
						while (retry)
						{
							retry = false;
							for (std::map<std::string, SCDirs*>::iterator it = scdirs_server.begin();
								it != scdirs_server.end(); ++it)
							{
								if (it->second->ref != nullptr
									&& it->second->ref != curr
									&& it->second->ref->ssetid == ssetid)
								{
									VSSLog("Releasing group shadow copy " + it->first + " orig_target=" + it->second->orig_target + " target=" + it->second->target + " (run_sc_refs_cleanup)", LL_DEBUG);
									release_shadowcopy(it->second, false, -1);
									found_ref = true;
									retry = true;
									break;
								}
							}
						}
					}
				}

				if (!found_ref)
				{
					VSSLog("Reference not found. Iterating over all start tokens and share names for deletion", LL_INFO);
					for (size_t k = 0; k < curr->starttokens.size() && !retry_all; ++k)
					{
						starttoken = curr->starttokens[k];
						SCDirs scd;
						scd.running = true;

						for (size_t j = 0; j < curr->sharenames.size() && !retry_all; ++j)
						{
							scd.dir = curr->sharenames[j];
							scd.starttime = Server->getTimeSeconds();
							if (sc_refs[i]->for_imagebackup)
							{
								scd.target = scd.dir;
								scd.fileserv = false;
							}
							else
							{
								scd.target = getShareDir(scd.dir);
								scd.fileserv = true;
							}

							if (filesrv != nullptr
								&& filesrv->hasActiveTransfersGen(scd.dir, starttoken, curr->cleanup_gen))
							{
								VSSLog(scd.dir + " orig_target=" + scd.orig_target + " target=" + scd.target + " starttoken="+ starttoken+ " gen="+convert(curr->cleanup_gen)+" still in use. Not releasing. (2)", LL_DEBUG);
								in_use = true;
								break;
							}

							scd.ref = sc_refs[i];

							size_t orig_size = sc_refs.size();

							release_shadowcopy(&scd, false, -1, &scd);

							if (sc_refs.size() != orig_size)
							{
								retry_all = true;
								break;
							}
						}
					}
				}

				if (in_use)
					continue;

				retry_all = true;
				break;
			}
		}
	}

	if (!has_cleanup)
	{
		sc_refs_cleanup = false;
	}
}

bool IndexThread::finishCbt(std::string volume, int shadow_id, std::string snap_volume,
	bool for_image_backup, std::string cbt_file, CbtType cbt_type)
{
#ifdef _WIN32
	ScopedUnlockCbtMutex unlock_cbt_mutex;

	if (!normalizeVolume(volume))
	{
		VSSLog("Error normalizing volume. Input \"" + volume + "\" (2)", LL_ERROR);
		return false;
	}

	HANDLE hVolume = CreateFileA(("\\\\.\\" + volume).c_str(), GENERIC_READ | GENERIC_WRITE,
		FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING,
		FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS, NULL);

	if (hVolume == INVALID_HANDLE_VALUE)
	{
		VSSLog("Error opening volume " + volume + ". " + os_last_error_str(), LL_ERROR);
		return false;
	}

	ScopedDisableBackgroundPrio disable_background_prio(background_prio.get());

	ScopedCloseWindowsHandle hclose(hVolume);

	if (FlushFileBuffers(hVolume) == FALSE)
	{
		std::string errmsg;
		int64 err = os_last_error(errmsg);
		VSSLog("Flushing volume " + volume + " failed: " + errmsg + " (code: " + convert(err) + ")", LL_ERROR);
		return false;
	}

	HANDLE hSnapVolume = INVALID_HANDLE_VALUE;
	if (!snap_volume.empty())
	{
		hSnapVolume = CreateFileA(snap_volume.c_str(), GENERIC_READ | GENERIC_WRITE,
			FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING,
			FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS, NULL);

		if (hSnapVolume == INVALID_HANDLE_VALUE)
		{
			VSSLog("Error opening volume snapshot of " + volume + " at " + snap_volume + ". " + os_last_error_str(), LL_ERROR);
			return false;
		}
	}

	ScopedCloseWindowsHandle hclosesnap(hSnapVolume);

	GET_LENGTH_INFORMATION lengthInfo;
	DWORD retBytes;
	BOOL b = DeviceIoControl(hVolume, IOCTL_DISK_GET_LENGTH_INFO, NULL, 0, &lengthInfo, sizeof(lengthInfo),
		&retBytes, NULL);

	if (!b)
	{
		std::string errmsg;
		int64 err = os_last_error(errmsg);
		VSSLog("Getting length information for volume " + volume + " failed: " + errmsg + " (code: " + convert(err) + ")", LL_ERROR);
		return false;
	}

	STORAGE_PROPERTY_QUERY alignmentQuery;
	alignmentQuery.QueryType = PropertyStandardQuery;
	alignmentQuery.PropertyId = StorageAccessAlignmentProperty;
	STORAGE_ACCESS_ALIGNMENT_DESCRIPTOR alignmentDescr = {};
	b = DeviceIoControl(hVolume, IOCTL_STORAGE_QUERY_PROPERTY, &alignmentQuery, sizeof(alignmentQuery), &alignmentDescr, sizeof(alignmentDescr),
		&retBytes, NULL);

	DWORD BytesPerSector = 4096;
	if (!b)
	{
		VSSLog("Error getting storage alignemnt property. " + os_last_error_str() + ". Fallback...", LL_DEBUG);

		DISK_GEOMETRY disk_geometry = {};
		b = DeviceIoControl(hVolume, IOCTL_DISK_GET_DRIVE_GEOMETRY, NULL, 0, &disk_geometry, sizeof(disk_geometry),
			&retBytes, NULL);

		if (!b)
		{
			std::string errmsg;
			int64 err = os_last_error(errmsg);
			VSSLog("Getting disk geometry of volume " + volume + " failed: " + errmsg + " (code: " + convert(err) + ")", LL_ERROR);
			return false;
		}

		BytesPerSector = disk_geometry.BytesPerSector;
	}
	else
	{
		BytesPerSector = alignmentDescr.BytesPerPhysicalSector;
	}

	DWORD sectors_per_cluster;
	DWORD bytes_per_sector;
	DWORD NumberOfFreeClusters;
	DWORD TotalNumberOfClusters;
	b = GetDiskFreeSpaceW((Server->ConvertToWchar(volume) + L"\\").c_str(), &sectors_per_cluster, &bytes_per_sector, &NumberOfFreeClusters, &TotalNumberOfClusters);
	if (!b)
	{
		VSSLog("Error in GetDiskFreeSpaceW. " + os_last_error_str(), LL_ERROR);
		return false;
	}
	DWORD blocksize = bytes_per_sector * sectors_per_cluster;

	ULONGLONG bitmapBlocks = lengthInfo.Length.QuadPart / URBT_BLOCKSIZE + (lengthInfo.Length.QuadPart % URBT_BLOCKSIZE == 0 ? 0 : 1);

	size_t bitmapBytesWoMagic = bitmapBlocks / 8 + (bitmapBlocks % 8 == 0 ? 0 : 1);

	DWORD bitmapSectorSize = BytesPerSector - URBT_MAGIC_SIZE;

	size_t bitmapBytes = (bitmapBytesWoMagic / bitmapSectorSize) * BytesPerSector
		+ ((bitmapBytesWoMagic % bitmapSectorSize != 0) ? (URBT_MAGIC_SIZE + bitmapBytesWoMagic % bitmapSectorSize) : 0);

	std::vector<char> buf;
	buf.resize(2 * sizeof(DWORD));

	DWORD bytesReturned;
	b = DeviceIoControl(hVolume, IOCTL_URBCT_RETRIEVE_BITMAP, NULL, 0, buf.data(), static_cast<DWORD>(buf.size()), &bytesReturned, NULL);

	if (!b && GetLastError() != ERROR_MORE_DATA)
	{
		std::string errmsg;
		int64 err = os_last_error(errmsg);
		VSSLog("Getting changed block data from volume " + volume + " failed: " + errmsg + " (code: " + convert(err) + ")", LL_ERROR);
		return false;
	}

	PURBCT_BITMAP_DATA bitmap_data = reinterpret_cast<PURBCT_BITMAP_DATA>(buf.data());

	if (bitmap_data->BitmapSize < bitmapBytes)
	{
		VSSLog("Did not track enough (volume resize?). Tracked " + convert((int)bitmap_data->BitmapSize) + " should track " + convert(bitmapBytes) + ". "
			"CBT will disable itself once this area is written to and CBT will reengage itself.", LL_INFO);
	}
	else
	{
		bitmapBytes = bitmap_data->BitmapSize;
	}

	buf.resize(2 * sizeof(DWORD) + bitmapBytes);

	b = DeviceIoControl(hVolume, IOCTL_URBCT_RETRIEVE_BITMAP, NULL, 0, buf.data(), static_cast<DWORD>(buf.size()), &bytesReturned, NULL);

	if (!b)
	{
		std::string errmsg;
		int64 err = os_last_error(errmsg);
		VSSLog("Getting changed block data from volume " + volume + " failed (2): " + errmsg + " (code: " + convert(err) + ")", LL_ERROR);
		return false;
	}

	std::vector<char> buf_snap;
	if (hSnapVolume != INVALID_HANDLE_VALUE)
	{
		buf_snap.resize(buf.size());

		b = DeviceIoControl(hSnapVolume, IOCTL_URBCT_RETRIEVE_BITMAP, NULL, 0, buf_snap.data(), static_cast<DWORD>(buf_snap.size()), &bytesReturned, NULL);

		if (!b && GetLastError() != ERROR_MORE_DATA)
		{
			std::string errmsg;
			int64 err = os_last_error(errmsg);
			VSSLog("Getting changed block data from shadow copy " + snap_volume + " failed: " + errmsg + " (code: " + convert(err) + ")", LL_ERROR);
			return false;
		}

		if (!b)
		{
			buf_snap.resize(2 * sizeof(DWORD) +
				reinterpret_cast<PURBCT_BITMAP_DATA>(buf_snap.data())->BitmapSize);

			b = DeviceIoControl(hSnapVolume, IOCTL_URBCT_RETRIEVE_BITMAP, NULL, 0, buf_snap.data(), static_cast<DWORD>(buf_snap.size()), &bytesReturned, NULL);

			if (!b)
			{
				std::string errmsg;
				int64 err = os_last_error(errmsg);
				VSSLog("Getting changed block data from shadow copy " + snap_volume + " failed: " + errmsg + " (code: " + convert(err) + ") -2", LL_ERROR);
				return false;
			}
		}
	}

	bitmap_data = reinterpret_cast<PURBCT_BITMAP_DATA>(buf.data());
	char* urbackupcbt_magic = URBT_MAGIC;

	for (DWORD i = bitmap_data->BitmapSize; i < bitmapBytes;)
	{
		if (i % bitmap_data->SectorSize == 0)
		{
			memcpy(&bitmap_data->Bitmap[i], urbackupcbt_magic, URBT_MAGIC_SIZE);
			i += URBT_MAGIC_SIZE;
			continue;
		}

		bitmap_data->Bitmap[i] = 0xFF;

		++i;
	}

	PURBCT_BITMAP_DATA snap_bitmap_data = reinterpret_cast<PURBCT_BITMAP_DATA>(buf_snap.data());

	DWORD RealBitmapSize = 0;
	DWORD RealVssBitmapSize = 0;

	int64 changed_bytes_sc = 0;

	for (DWORD i = 0; i < bitmap_data->BitmapSize; i += bitmap_data->SectorSize)
	{
		if (memcmp(&bitmap_data->Bitmap[i], urbackupcbt_magic, URBT_MAGIC_SIZE) != 0)
		{
			VSSLog("UrBackup cbt magic wrong at pos " + convert((size_t)i), LL_ERROR);
			return false;
		}

		DWORD tr = (std::min)(bitmap_data->BitmapSize - i - URBT_MAGIC_SIZE,
			bitmap_data->SectorSize - URBT_MAGIC_SIZE);
		RealBitmapSize += tr;
	}

	if (hSnapVolume != INVALID_HANDLE_VALUE)
	{
		for (DWORD i = 0; i < snap_bitmap_data->BitmapSize; i += snap_bitmap_data->SectorSize)
		{
			if (memcmp(&snap_bitmap_data->Bitmap[i], urbackupcbt_magic, URBT_MAGIC_SIZE) != 0)
			{
				VSSLog("UrBackup cbt snap magic wrong at pos " + convert((size_t)i), LL_ERROR);
				return false;
			}

			DWORD tr = (std::min)(snap_bitmap_data->BitmapSize - i - URBT_MAGIC_SIZE,
				snap_bitmap_data->SectorSize - URBT_MAGIC_SIZE);
			RealVssBitmapSize += tr;
		}

		VSSLog("VSS geometry. "
			"Orig sector size=" + convert(static_cast<int64>(bitmap_data->SectorSize)) +
			" Orig bitmap size=" + convert(static_cast<int64>(bitmap_data->BitmapSize)) +
			" VSS sector size=" + convert(static_cast<int64>(snap_bitmap_data->SectorSize)) +
			" VSS bitmap size=" + convert(static_cast<int64>(snap_bitmap_data->BitmapSize)) +
			" RealVssBitmapSize=" + convert(static_cast<int64>(RealVssBitmapSize)) +
			" RealBitmapSize=" + convert(static_cast<int64>(RealBitmapSize)), LL_DEBUG);

		for (DWORD i = 0, s = 0; i < bitmap_data->BitmapSize
			&& s < snap_bitmap_data->BitmapSize;)
		{
			if (i % bitmap_data->SectorSize == 0)
			{
				if (memcmp(&bitmap_data->Bitmap[i], urbackupcbt_magic, URBT_MAGIC_SIZE) != 0)
				{
					VSSLog("UrBackup cbt magic wrong at pos " + convert((size_t)i) + " (2)", LL_ERROR);
					return false;
				}

				i += URBT_MAGIC_SIZE;
				continue;
			}

			if (s % snap_bitmap_data->SectorSize == 0)
			{
				if (memcmp(&snap_bitmap_data->Bitmap[s], urbackupcbt_magic, URBT_MAGIC_SIZE) != 0)
				{
					VSSLog("UrBackup cbt snap magic wrong at pos " + convert((size_t)s) + " (2)", LL_ERROR);
					return false;
				}
				s += URBT_MAGIC_SIZE;
				continue;
			}

			BYTE b = snap_bitmap_data->Bitmap[s];
			bitmap_data->Bitmap[i] |= b;

			++s;
			++i;
		}

		for (DWORD i = 0, s = 0; i < bitmap_data->BitmapSize;)
		{
			if (i % bitmap_data->SectorSize == 0)
			{
				i += URBT_MAGIC_SIZE;
				continue;
			}

			BYTE b = snap_bitmap_data->Bitmap[s];
			while (b > 0)
			{
				if (b & 1)
				{
					changed_bytes_sc += URBT_BLOCKSIZE;
				}
				b >>= 1;
			}

			++i;
		}

		VSSLog("Change block tracking reports " + PrettyPrintBytes(changed_bytes_sc) + " have changed on shadow copy " + snap_volume, LL_DEBUG);

		b = DeviceIoControl(hVolume, IOCTL_URBCT_APPLY_BITMAP, buf_snap.data(), static_cast<DWORD>(buf_snap.size()), NULL, 0, &bytesReturned, NULL);

		if (!b)
		{
			std::string errmsg;
			int64 err = os_last_error(errmsg);
			VSSLog("Applying shadow copy changes to " + volume + " failed: " + errmsg + " (code: " + convert(err) + ")", LL_ERROR);
			return false;
		}
	}

	VSSLog("Change block tracking active on volume " + volume, LL_INFO);

	std::vector<std::string> cbt_paths = get_cbt_paths();

	if (!readMergeBitmap("urbackup\\hdat_other_" + conv_filename(strlower(volume)) + ".cbt", bitmap_data))
	{
		VSSLog("Error reading last bitmap data for CBT from other", LL_ERROR);
		return false;
	}

	for (size_t i = 0; i < cbt_paths.size(); ++i)
	{
		std::string cbt_path = cbt_paths[i];
		if (cbt_path != Server->getServerWorkingDir() + os_file_sep() + "urbackup")
		{
			if (!saveMergeBitmap(cbt_path + "\\hdat_other_" + conv_filename(strlower(volume)) + ".cbt",
				bitmap_data))
			{
				VSSLog("Error reading last bitmap data for CBT for other", LL_ERROR);
				return false;
			}
		}
	}

	if (!snap_volume.empty())
	{
		HKEY profile_list;
		if (RegOpenKeyExW(HKEY_LOCAL_MACHINE,
			Server->ConvertToWchar("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList").c_str(),
			0, KEY_READ, &profile_list) == ERROR_SUCCESS)
		{
			wchar_t buf[300];

			for (DWORD i = 0; RegEnumKeyW(profile_list, i, buf, sizeof(buf)) == ERROR_SUCCESS; ++i)
			{
				DWORD rsize = sizeof(buf) * sizeof(wchar_t);
				if (RegGetValueW(profile_list, std::wstring(buf).c_str(), L"ProfileImagePath",
					RRF_RT_REG_SZ, NULL, buf, &rsize) == ERROR_SUCCESS)
				{
					std::string profile_path = Server->ConvertFromWchar(buf);

					if (next(profile_path, 0, volume))
					{
						profile_path.erase(0, volume.size());

						addFileToCbt(snap_volume + profile_path + "\\NTUSER.DAT", blocksize, bitmap_data);
						for (size_t n = 1; n < 100; ++n)
						{
							if (!addFileToCbt(snap_volume + profile_path + "\\ntuser.dat.LOG" + convert(n), blocksize, bitmap_data))
								break;
						}
					}
				}
			}

			RegCloseKey(profile_list);
		}

		if (strlower(volume) == "c:")
		{
			char* locations[] = { "\\Windows\\System32\\config\\SYSTEM",
				"\\Windows\\System32\\config\\SOFTWARE",
				"\\Windows\\System32\\config\\SECURITY",
				"\\Windows\\System32\\config\\SAM",
				"\\Windows\\System32\\config\\DEFAULT" };

			for (size_t i = 0; i < sizeof(locations) / sizeof(locations[0]); ++i)
			{
				std::string loc = locations[i];

				addFileToCbt(snap_volume + loc, blocksize, bitmap_data);
				for (size_t n = 1; n < 100; ++n)
				{
					if (!addFileToCbt(snap_volume + loc + "LOG" + convert(n), blocksize, bitmap_data))
						break;
				}
			}
		}
	}

	if (for_image_backup)
	{
		if (!saveMergeBitmap("urbackup\\hdat_file_" + conv_filename(strlower(volume)) + ".cbt", bitmap_data))
		{
			VSSLog("Error saving CBT bitmap for file backup", LL_ERROR);
			return false;
		}

		if (!readMergeBitmap("urbackup\\hdat_img_" + conv_filename(strlower(volume)) + ".cbt", bitmap_data))
		{
			VSSLog("Error reading last bitmap data for CBT for image backup", LL_ERROR);
			return false;
		}

		std::unique_ptr<IFsFile> hdat_img(ImageThread::openHdatF(volume, false));

		bool concurrent_active = false;

		if (hdat_img.get() == NULL)
		{
			hdat_img.reset(ImageThread::openHdatF(volume, true));

			if (hdat_img.get() != NULL)
			{
				concurrent_active = true;
			}
		}

		if (hdat_img.get() == NULL)
		{
			std::string errmsg;
			int64 err = os_last_error(errmsg);
			VSSLog("Cannot open image hash data file for change block tracking. " + errmsg + " (code: " + convert(err) + ")", LL_ERROR);
			return false;
		}

		hdat_img->Resize(sizeof(shadow_id) + RealBitmapSize * 8 * SHA256_DIGEST_SIZE);

		if (hdat_img->Write(0, reinterpret_cast<char*>(&shadow_id), sizeof(shadow_id)) != sizeof(shadow_id))
		{
			VSSLog("Error writing shadow id", LL_ERROR);
			return false;
		}

		{
			IScopedLock lock(cbt_shadow_id_mutex);
			cbt_shadow_ids[strlower(volume)] = shadow_id;
		}

		if (concurrent_active)
		{
			Server->wait(10000);
		}

		char zero_sha[SHA256_DIGEST_SIZE] = {};
		char zero_sha_read[sizeof(zero_sha)];

		VSSLog("Zeroing image hash data of volume " + volume + "...", LL_DEBUG);

		int64 changed_bytes = 0;

		DWORD curr_byte = 0;
		for (DWORD i = 0; i < bitmap_data->BitmapSize; i += bitmap_data->SectorSize)
		{
			for (DWORD j = i + URBT_MAGIC_SIZE; j < i + bitmap_data->SectorSize; ++j)
			{
				BYTE ch = bitmap_data->Bitmap[j];

				if (ch == 0)
				{
					++curr_byte;
					continue;
				}

				for (DWORD bit = 0; bit < 8; ++bit)
				{
					if ((ch & (1 << bit)) > 0)
					{
						int64 zero_pos = sizeof(shadow_id) + (curr_byte * 8 + bit) * SHA256_DIGEST_SIZE;
						if (!punchHoleOrZero(hdat_img.get(), zero_pos, zero_sha, zero_sha_read, sizeof(zero_sha)))
						{
							return false;
						}
						changed_bytes += URBT_BLOCKSIZE;
					}
				}

				++curr_byte;
			}
		}

		hdat_img->Sync();

		Server->deleteFile("urbackup\\hdat_img_" + conv_filename(strlower(volume)) + ".cbt");
	}
	else //!for_image_backup
	{
		if (!saveMergeBitmap("urbackup\\hdat_img_" + conv_filename(strlower(volume)) + ".cbt", bitmap_data))
		{
			VSSLog("Error saving CBT bitmap for image backup", LL_ERROR);
			return false;
		}

		if (!readMergeBitmap("urbackup\\hdat_file_" + conv_filename(strlower(volume)) + ".cbt", bitmap_data))
		{
			VSSLog("Error reading last bitmap data for CBT for image backup", LL_ERROR);
			return false;
		}

		{
			IScopedLock lock(cbt_shadow_id_mutex);
			++index_hdat_sequence_ids[strlower(volume)];
		}

		std::unique_ptr<IFsFile> hdat_file(Server->openFile("urbackup\\hdat_file_" + conv_filename(strlower(volume)) + ".dat", MODE_RW_CREATE_DELETE));

		if (hdat_file.get() == NULL)
		{
			std::string errmsg;
			int64 err = os_last_error(errmsg);
			VSSLog("Cannot open file hash data file for change block tracking. " + errmsg + " (code: " + convert(err) + ")", LL_ERROR);
			return false;
		}

		hdat_file->Resize(bitmap_data->BitmapSize * 8 * (sizeof(_u16) + chunkhash_single_size));

		VSSLog("Zeroing file hash data of volume " + volume + "...", LL_DEBUG);

		char zero_chunk[sizeof(_u16) + chunkhash_single_size] = {};
		char zero_chunk_read[sizeof(zero_chunk)];

		DWORD curr_byte = 0;
		bool last_bit_set = false;
		bool last_zeroed = false;
		for (DWORD i = 0; i < bitmap_data->BitmapSize; i += bitmap_data->SectorSize)
		{
			for (DWORD j = i + URBT_MAGIC_SIZE; j < i + bitmap_data->SectorSize; ++j)
			{
				BYTE ch = bitmap_data->Bitmap[j];

				if (ch == 0)
				{
					if (last_bit_set)
					{
						int64 zero_pos = ((int64)curr_byte * 8) * sizeof(zero_chunk);
						if (!punchHoleOrZero(hdat_file.get(), zero_pos, zero_chunk, zero_chunk_read, sizeof(zero_chunk)))
						{
							return false;
						}
					}

					last_bit_set = false;
					last_zeroed = false;

					++curr_byte;
					continue;
				}

				for (DWORD bit = 0; bit < 8; ++bit)
				{
					bool has_bit = (ch & (1 << bit)) > 0;
					if (has_bit
						|| last_bit_set)
					{
						if (!last_bit_set
							&& !last_zeroed)
						{
							int64 last_pos = (int64)curr_byte * 8 + bit;
							if (last_pos > 0)
							{
								--last_pos;

								int64 zero_pos = last_pos * sizeof(zero_chunk);
								if (!punchHoleOrZero(hdat_file.get(), zero_pos, zero_chunk, zero_chunk_read, sizeof(zero_chunk)))
								{
									return false;
								}
							}
						}

						int64 zero_pos = ((int64)curr_byte * 8 + bit) * sizeof(zero_chunk);
						if (!punchHoleOrZero(hdat_file.get(), zero_pos, zero_chunk, zero_chunk_read, sizeof(zero_chunk)))
						{
							return false;
						}
						last_zeroed = true;
					}
					else
					{
						last_zeroed = false;
					}
					last_bit_set = has_bit;
				}
				++curr_byte;
			}
		}

		hdat_file->Sync();

		Server->deleteFile("urbackup\\hdat_file_" + conv_filename(strlower(volume)) + ".cbt");
	} //for_image_backup

#ifndef _DEBUG
	b = DeviceIoControl(hVolume, IOCTL_URBCT_RESET_FINISH, NULL, 0, NULL, 0, &bytesReturned, NULL);
#endif

	if (!b)
	{
		std::string errmsg;
		int64 err = os_last_error(errmsg);
		VSSLog("Finishing change block tracking reset for volume " + volume + " failed: " + errmsg + " (code: " + convert(err) + ")", LL_DEBUG);
		return false;
	}

	return true;
#else
	std::string fs_dev = getMountDevice(volume);
	if (fs_dev.empty())
	{
		VSSLog("Cannot get device of mount " + volume + ". CBT disabled.", LL_INFO);
		return false;
	}

	if (cbt_file.empty())
	{
		Server->deleteFile("urbackup/hdat_file_" + conv_filename(fs_dev) + ".dat");
		Server->deleteFile("urbackup/hdat_img_" + conv_filename(fs_dev) + ".dat");
	}

	std::unique_ptr<IFsFile> hdat_file(Server->openFile("urbackup/hdat_file_" + conv_filename(fs_dev) + ".dat", MODE_RW_CREATE_DELETE));
	std::unique_ptr<IFsFile> hdat_img(ImageThread::openHdatF(fs_dev, false));

	if (hdat_img.get() == nullptr && hdat_file.get() == nullptr)
	{
		VSSLog("Error opening img and file backup hdat file", LL_ERROR);
		return false;
	}

	std::string orig_dev = trim(getFile(snap_volume + "-dev"));

	if (orig_dev.empty())
	{
		VSSLog("Error getting snapshotted device from " + snap_volume + "-dev", LL_ERROR);
		return false;
	}

	std::unique_ptr<IFile> volfile(Server->openFile(orig_dev, MODE_READ_DEVICE));

	if (volfile.get() == nullptr)
	{
		VSSLog("Error opening volume file " + orig_dev, LL_ERROR);
		return false;
	}


	if (hdat_file.get() != nullptr)
	{
		hdat_file->Resize(((volfile->Size() + c_checkpoint_dist - 1) / c_checkpoint_dist) * (sizeof(_u16) + chunkhash_single_size));
	}

	if (hdat_img.get() != nullptr)
	{
		hdat_img->Resize(sizeof(shadow_id) + ((volfile->Size() + c_checkpoint_dist - 1) / c_checkpoint_dist) * SHA256_DIGEST_SIZE);
	}

	if (cbt_file.empty())
	{
		int64 current_era = -1;

		if (cbt_type == CbtType_Era)
		{
			std::string era_status;
			if (os_popen("dmsetup status \"" + orig_dev + "\"", era_status)==0)
			{
				std::vector<std::string> toks;
				Tokenize(era_status, toks, " ");
				if (toks.size() > 1)
				{
					current_era = watoi64(toks[2]);
				}
			}
		}

		if (hdat_img.get() != nullptr)
		{
			if (hdat_img->Write(0, reinterpret_cast<char*>(&shadow_id), sizeof(shadow_id)) != sizeof(shadow_id))
			{
				VSSLog("Error writing shadow id (3)", LL_ERROR);
				return false;
			}

			{
				IScopedLock lock(cbt_shadow_id_mutex);
				cbt_shadow_ids[strlower(volume)] = shadow_id;
			}
		}

		if (hdat_file.get() != nullptr)
		{
			IScopedLock lock(cbt_shadow_id_mutex);
			++index_hdat_sequence_ids[strlower(volume)];
		}

		if (hdat_img.get() != nullptr)
		{
			if (!hdat_img->Sync())
			{
				VSSLog("Error syncing hdat_img file -1", LL_ERROR);
				return false;
			}
		}

		if (hdat_file.get() != nullptr)
		{
			if (!hdat_file->Sync())
			{
				VSSLog("Error syncing hdat_file file -1", LL_ERROR);
				return false;
			}
		}

		if (cbt_type == CbtType_Era
			&& current_era>0)
		{
			if (!finishCbtEra2(hdat_file.get(), current_era))
			{
				return false;
			}

			if (!finishCbtEra2(hdat_img.get(), current_era))
			{
				return false;
			}
		}

		return true;
	}
	bool ret = false;
	int64 hdat_file_era;
	int64 hdat_img_era;
	if (cbt_type == CbtType_Datto)
	{
		ret = finishCbtDatto(volfile.get(), hdat_file.get(), hdat_img.get(), volume, shadow_id, snap_volume, for_image_backup, cbt_file);
	}
	else if (cbt_type == CbtType_Era)
	{
		ret = finishCbtEra(hdat_file.get(), hdat_img.get(), volume, shadow_id, snap_volume, for_image_backup, cbt_file,
			hdat_file_era, hdat_img_era);
	}

	if (ret)
	{
		if (hdat_img.get() != nullptr)
		{
			if (!hdat_img->Sync())
			{
				VSSLog("Error syncing hdat_img file", LL_ERROR);
				return false;
			}
		}

		if (hdat_file.get() != nullptr)
		{
			if (!hdat_file->Sync())
			{
				VSSLog("Error syncing hdat_file file", LL_ERROR);
				return false;
			}
		}


		if (cbt_type == CbtType_Era)
		{
			if (!finishCbtEra2(hdat_file.get(), hdat_file_era))
			{
				return false;
			}

			if (!finishCbtEra2(hdat_img.get(), hdat_img_era))
			{
				return false;
			}
		}
	}

	if (cbt_type == CbtType_Datto)
	{
		Server->deleteFile(cbt_file);
	}

	return ret;
#endif
}

#ifndef _WIN32
bool IndexThread::finishCbtDatto(IFile* volfile, IFsFile* hdat_file, IFsFile* hdat_img, std::string volume, int shadow_id,
	std::string snap_volume, bool for_image_backup, std::string cbt_file)
{
	std::string datto_dev = trim(getFile(snap_volume + "-dev"));

	if (datto_dev.empty())
	{
		VSSLog("Error getting datto device from " + snap_volume + "-dev", LL_ERROR);
		return false;
	}

	int datto_num = watoi(getafter("/dev/datto", datto_dev));

	int fd = open("/dev/datto-ctl", O_RDONLY);
	if (fd == -1)
	{
		VSSLog("Error opening /dev/datto-ctl", LL_ERROR);
		return false;
	}

	dattobd_info dbd_info = {};
	dbd_info.minor = datto_num;

	int rc = ioctl(fd, IOCTL_DATTOBD_INFO, &dbd_info);

	close(fd);

	if (rc == -1)
	{
		VSSLog("Error running IOCTL_DATTOBD_INFO for datto dev " + convert(datto_num), LL_ERROR);
		return false;
	}

	std::unique_ptr<IFile> cowfile(Server->openFile(cbt_file, MODE_READ));

	if (cowfile.get() == nullptr)
	{
		VSSLog("Error opening datto cow file at " + cbt_file, LL_ERROR);
		return false;
	}


	char header_buf[4096];
	if (cowfile->Read(header_buf, static_cast<_u32>(sizeof(header_buf))) != sizeof(header_buf))
	{
		VSSLog("Error reading datto header from " + cbt_file, LL_ERROR);
		return false;
	}


	dattobd_header dbd_header;
	memcpy(&dbd_header, header_buf, sizeof(dbd_header));

	if (dbd_header.magic != DATTO_MAGIC)
	{
		VSSLog("Datto magic wrong at " + cbt_file, LL_ERROR);
		return false;
	}

	if (memcmp(dbd_header.uuid, dbd_info.uuid, DATTO_UUID_SIZE) != 0)
	{
		VSSLog("Snapshot and cbt file uuids differ (cbt_file=" + cbt_file + " snapshot=" + datto_dev + ")", LL_ERROR);
		return false;
	}

	if (dbd_header.seqid + 1 != dbd_info.seqid)
	{
		VSSLog("Snapshot not at correct seqid expected " + convert(dbd_info.seqid) + " got " + convert(dbd_header.seqid + 1), LL_ERROR);
		return false;
	}

	if (hdat_img != nullptr)
	{
		if (hdat_img->Write(0, reinterpret_cast<char*>(&shadow_id), sizeof(shadow_id)) != sizeof(shadow_id))
		{
			VSSLog("Error writing shadow id", LL_ERROR);
			return false;
		}

		{
			IScopedLock lock(cbt_shadow_id_mutex);
			cbt_shadow_ids[strlower(volume)] = shadow_id;
		}
	}

	if (hdat_file != nullptr)
	{
		IScopedLock lock(cbt_shadow_id_mutex);
		++index_hdat_sequence_ids[strlower(volume)];
	}

	const int64 dbd_block_size = 4096;
	char zero_sha[SHA256_DIGEST_SIZE] = {};
	char zero_sha_read[sizeof(zero_sha)];
	char zero_chunk[sizeof(_u16) + chunkhash_single_size] = {};
	char zero_chunk_read[sizeof(zero_chunk)];
	bool last_bit_set = false;
	bool last_zeroed = false;
	int64 num_blocks = (volfile->Size() + dbd_block_size - 1) / dbd_block_size;

	VSSLog("Zeroing file hash data of volume " + volume + "...", LL_DEBUG);

	for (int64 i = 0; i < num_blocks;)
	{
		int64 toread_blocks = (std::min)(num_blocks - i, static_cast<int64>(sizeof(header_buf) / sizeof(int64)));
		_u32 toread_bytes = static_cast<_u32>(toread_blocks * sizeof(int64));
		if (cowfile->Read(header_buf, toread_bytes) != toread_bytes)
		{
			VSSLog("Error reading change block information from " + cbt_file, LL_ERROR);
			return false;
		}

		int64 block_start = i;

		for (; i < block_start + toread_blocks; ++i)
		{
			uint64 block_map;
			memcpy(&block_map, header_buf + (i - block_start) * sizeof(int64), sizeof(block_map));

			int64 cbt_pos = (i * dbd_block_size) / c_checkpoint_dist;

			if (block_map && hdat_img != nullptr)
			{
				if (!punchHoleOrZero(hdat_img, sizeof(shadow_id) + cbt_pos * SHA256_DIGEST_SIZE,
					zero_sha, zero_sha_read, sizeof(zero_sha)))
				{
					return false;
				}
			}

			if (hdat_file != nullptr)
			{
				bool has_bit = (block_map > 0);
				if (has_bit
					|| last_bit_set)
				{
					if (!last_bit_set
						&& !last_zeroed)
					{
						int64 last_pos = cbt_pos;
						if (last_pos > 0)
						{
							--last_pos;
							if (!punchHoleOrZero(hdat_file, last_pos * sizeof(zero_chunk), zero_chunk, zero_chunk_read, sizeof(zero_chunk)))
							{
								return false;
							}
						}
					}

					if (!punchHoleOrZero(hdat_file, cbt_pos * sizeof(zero_chunk), zero_chunk, zero_chunk_read, sizeof(zero_chunk)))
					{
						return false;
					}
					last_zeroed = true;
				}
				else
				{
					last_zeroed = false;
				}
				last_bit_set = has_bit;
			}
		}
	}

	return true;
}
#endif

bool IndexThread::finishCbtEra(IFsFile* hdat_file, IFsFile* hdat_img, std::string volume, int shadow_id,
	std::string snap_volume, bool for_image_backup, std::string cbt_file,
	int64& hdat_file_era, int64& hdat_img_era)
{
	hdat_file_era = -1;
	if(hdat_file!=nullptr)
		hdat_file_era = watoi64(trim(getFile(hdat_file->getFilename() + ".era")));
	hdat_img_era = -1;
	if(hdat_img!=nullptr)
		hdat_img_era = watoi64(trim(getFile(hdat_img->getFilename() + ".era")));

	std::string dev_file = getMountDevice(volume);

	if (dev_file.empty())
	{
		VSSLog("Error getting device of file system at \"" + volume + "\"", LL_WARNING);
		return false;
	}

	dev_file += "-a31725acca86421d-clone";

	{
		std::string era_err;
		if (os_popen("dmsetup message \"" + dev_file + "\" 0 take_metadata_snap 2>&1", era_err) != 0)
		{
			VSSLog("Error taking dm-era metadata snapshot of volume \"" + dev_file + "\": " + trim(era_err), LL_WARNING);
			return false;
		}
	}

	if (hdat_img != nullptr)
	{
		if (hdat_img->Write(0, reinterpret_cast<char*>(&shadow_id), sizeof(shadow_id)) != sizeof(shadow_id))
		{
			VSSLog("Error writing shadow id", LL_ERROR);
			return false;
		}

		{
			IScopedLock lock(cbt_shadow_id_mutex);
			cbt_shadow_ids[strlower(volume)] = shadow_id;
		}
	}

	if (hdat_file != nullptr)
	{
		IScopedLock lock(cbt_shadow_id_mutex);
		++index_hdat_sequence_ids[strlower(volume)];
	}

	VSSLog("Zeroing file hash data of volume " + volume + "...", LL_DEBUG);

#ifndef _WIN32
#define _popen popen
#define _pclose pclose
#endif
#ifdef __ANDROID__
	POFILE* pin = NULL;
#endif
	FILE* in = nullptr;

	std::string cmd = "era_dump --logical \"" + cbt_file + "\"";
#ifdef __ANDROID__
	pin = and_popen(cmd.c_str(), "r");
	if (pin != NULL) in = pin->fp;
#else
	in = _popen(cmd.c_str(), "re");
	if(in==nullptr)
		in = _popen(cmd.c_str(), "r");
#endif

	if (in == nullptr)
	{
		VSSLog("Error running command \"era_dump --logical "+cbt_file+"\"", LL_WARNING);
		return false;
	}

	char buf[4096];
	size_t read;
	bool xml_node_close;
	std::string xml_node_name;
	str_map xml_node_attrs;
	std::string xml_node_attr_key;
	std::string xml_node_attr_val;
	enum XmlState
	{
		XmlState_Text,
		XmlState_NodeName1,
		XmlState_NodeNameX,
		XmlState_NodeAttrKey,
		XmlState_NodeAttrValueQuote,
		XmlState_NodeAttrValue
	} xml_state = XmlState_Text;

	enum XmlLoc
	{
		XmlLoc_Init,
		XmlLoc_EraArray
	} xml_loc = XmlLoc_Init;

	int64 current_era = -1;
	int64 block_size = -1;

	char zero_sha[SHA256_DIGEST_SIZE] = {};
	char zero_sha_read[sizeof(zero_sha)];
	char zero_chunk[sizeof(_u16) + chunkhash_single_size] = {};
	char zero_chunk_read[sizeof(zero_chunk)];
	bool last_bit_set = false;
	bool last_zeroed = false;

	do
	{
		read = fread(buf, 1, sizeof(buf), in);
		for (size_t i = 0; i < read;++i)
		{
			bool node_finished = false;

			char ch = buf[i];
			switch (xml_state)
			{
			case XmlState_Text:
				if (ch == '<')
				{
					xml_node_name.clear();
					xml_state = XmlState_NodeName1;
				}
				break;
			case XmlState_NodeName1:
			{
				if (ch == '/')
				{
					xml_node_close = true;
					xml_state = XmlState_NodeNameX;
				}
				else if (ch == '>')
				{
					xml_state = XmlState_Text;
					node_finished = true;
				}
				else
				{
					xml_node_close = false;
					if (ch != ' ' && ch != '\t')
						xml_node_name += ch;
					xml_state = XmlState_NodeNameX;
				}
			} break;
			case XmlState_NodeNameX:
			{
				if (ch == '>')
				{
					xml_state = XmlState_Text;
					node_finished = true;
				}
				else if (ch != ' ' && ch != '\t')
				{
					xml_node_name += ch;
				}
				else if (!xml_node_name.empty()
					&& (ch == ' ' || ch == '\t'))
				{
					xml_state = XmlState_NodeAttrKey;
					xml_node_attr_key.clear();
					xml_node_attrs.clear();
				}
			}break;
			case XmlState_NodeAttrKey:
			{
				if (ch == '>')
				{
					xml_state = XmlState_Text;
					node_finished = true;
				}
				else if (ch == '=')
					xml_state = XmlState_NodeAttrValueQuote;
				else if (ch != ' ' && ch != ' ')
					xml_node_attr_key += ch;				
			} break;
			case XmlState_NodeAttrValueQuote:
			{
				if (ch == '>')
				{
					xml_state = XmlState_Text;
					node_finished = true;
				}
				else if (ch == '"')
				{
					xml_node_attr_val.clear();
					xml_state = XmlState_NodeAttrValue;
				}
			} break;
			case XmlState_NodeAttrValue:
			{
				if (ch != '"')
				{
					xml_node_attr_val += ch;
				}
				else
				{
					xml_node_attrs[xml_node_attr_key] = xml_node_attr_val;
					xml_state = XmlState_NodeAttrKey;
					xml_node_attr_key.clear();
				}
			}break;

			}

			if (node_finished)
			{
				if (xml_loc == XmlLoc_Init &&
					xml_node_name == "superblock"
					&& !xml_node_close)
				{
					current_era = watoi64(xml_node_attrs["current_era"]);
					block_size = watoi64(xml_node_attrs["block_size"])*512;

					// take_metadata_snap increases era by one
					if(current_era>0)
						current_era--;

					if (block_size > c_checkpoint_dist)
					{
						VSSLog("Era block size too large (" + PrettyPrintBytes(block_size) + "). Must be smaller or equal to " + PrettyPrintBytes(c_checkpoint_dist), LL_WARNING);
						return false;
					}
					else if (block_size == 0)
					{
						VSSLog("Era block size not defined", LL_WARNING);
						return false;
					}
					else if (current_era <= 0)
					{
						VSSLog("Current era not defined", LL_WARNING);
						return false;
					}
				}
				else if (xml_node_name == "era_array")
				{
					xml_loc = xml_node_close ? XmlLoc_Init : XmlLoc_EraArray;
				}
				else if (xml_loc == XmlLoc_EraArray &&
						xml_node_name == "era")
				{
					if (current_era<=0)
					{
						VSSLog("Did not get superblock before era block", LL_WARNING);
						return false;
					}

					int64 block_nr = watoi64(xml_node_attrs["block"]);
					int64 block_era = watoi64(xml_node_attrs["era"]);

					int64 cbt_pos = (block_nr * block_size) / c_checkpoint_dist;

					if (block_era >= hdat_img_era && hdat_img != nullptr)
					{
						if (!punchHoleOrZero(hdat_img, sizeof(shadow_id) + cbt_pos * SHA256_DIGEST_SIZE,
								zero_sha, zero_sha_read, sizeof(zero_sha)))
						{
							return false;
						}
					}

					if (hdat_file != nullptr)
					{
						bool has_bit = (block_era >= hdat_file_era);
						if (has_bit
							|| last_bit_set)
						{
							if (!last_bit_set
								&& !last_zeroed)
							{
								int64 last_pos = cbt_pos;
								if (last_pos > 0)
								{
									--last_pos;
									if (!punchHoleOrZero(hdat_file, last_pos * sizeof(zero_chunk), zero_chunk, zero_chunk_read, sizeof(zero_chunk)))
									{
										return false;
									}
								}
							}

							if (!punchHoleOrZero(hdat_file, cbt_pos * sizeof(zero_chunk), zero_chunk, zero_chunk_read, sizeof(zero_chunk)))
							{
								return false;
							}
							last_zeroed = true;
						}
						else
						{
							last_zeroed = false;
						}
						last_bit_set = has_bit;
					}
				}
			}
		}
	}
	while (read == sizeof(buf));


	int rc;
#ifdef __ANDROID__
	rc = and_pclose(pin);
#else
	rc = _pclose(in);
#endif
	if (rc != 0)
	{
		VSSLog("Error running command \"era_dump --logical " + cbt_file + "\" rc " + convert(rc), LL_WARNING);
		return false;
	}
		
	std::string era_err;
	if (os_popen("dmsetup message \"" + dev_file + "\" 0 drop_metadata_snap 2>&1", era_err) != 0)
	{
		VSSLog("Error dropping dm-era metadata snapshot of volume \""+ dev_file +"\": "+trim(era_err), LL_WARNING);
		return false;
	}

	if (hdat_file != nullptr)
		hdat_file_era = current_era;
	if (hdat_img != nullptr)
		hdat_img_era = current_era;

	return true;
}


bool IndexThread::finishCbtEra2(IFsFile* hdat, int64 hdat_era)
{
	if (hdat != nullptr)
	{
		std::unique_ptr<IFile> hdat_file_era_new(Server->openFile(hdat->getFilename() + ".era.new", MODE_WRITE));

		if (hdat_file_era_new.get() == nullptr)
		{
			VSSLog("Error opening file at " + hdat->getFilename() + ".era.new. " + os_last_error_str(), LL_ERROR);
			return false;
		}

		if (hdat_file_era_new->Write(convert(hdat_era)) != convert(hdat_era).size())
		{
			VSSLog("Error writing to " + hdat->getFilename() + ".era.new", LL_ERROR);
			return false;
		}

		if (!hdat_file_era_new->Sync())
		{
			VSSLog("Error syncing " + hdat->getFilename() + ".era.new. " + os_last_error_str(), LL_ERROR);
			return false;
		}

		if (!os_rename_file(hdat->getFilename() + ".era.new",
				hdat->getFilename() + ".era"))
		{
			VSSLog("Error renaming " + hdat->getFilename() + ".era.new to "+hdat->getFilename() + ".era. " + os_last_error_str(), LL_ERROR);
			return false;
		}
	}

	return true;
}

bool IndexThread::disableCbt(std::string volume)
{
#ifdef _WIN32
	if (!normalizeVolume(volume))
	{
		return true;
	}

	Server->Log("Disabling CBT on volume \"" + volume + "\"", LL_DEBUG);

	GUID rnd = randomGuid();
	std::string rndfn = "urbackup\\hdat_file_" + conv_filename(volume) + "_" + guidToString(rnd) + ".dat";
	if (os_rename_file("urbackup\\hdat_file_" + conv_filename(volume) + ".dat", rndfn))
	{
		Server->deleteFile(rndfn);
	}
	Server->deleteFile(ImageThread::hdatFn(volume));

	HANDLE hVolume = CreateFileA(("\\\\.\\" + volume).c_str(), GENERIC_READ | GENERIC_WRITE,
		FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING,
		FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS, NULL);

	if (hVolume != INVALID_HANDLE_VALUE)
	{
		ScopedCloseWindowsHandle hclose(hVolume);
		DWORD bytesReturned;
		DeviceIoControl(hVolume, IOCTL_URBCT_MARK_ALL, NULL, 0, NULL, 0, &bytesReturned, NULL);
	}

	return !FileExists("urbackup\\hdat_file_" + conv_filename(volume) + ".dat")
		&& !FileExists(ImageThread::hdatFn(volume));
#else
	Server->Log("Disabling CBT on volume \"" + volume + "\"", LL_DEBUG);

	std::string mountfn = getMountDevice(volume);
	if(!mountfn.empty())
	{
		Server->deleteFile("urbackup/hdat_file_"+conv_filename(mountfn)+".dat");
		Server->deleteFile("urbackup/hdat_img_"+conv_filename(mountfn)+".dat");
	}
	else
	{
		Server->deleteFile("urbackup/hdat_file_"+conv_filename(volume)+".dat");
		Server->deleteFile("urbackup/hdat_img_"+conv_filename(volume)+".dat");
	}
	return true;
#endif
}

void IndexThread::enableCbtVol(std::string volume, bool install, bool reengage)
{
#ifdef _WIN32
	if (!normalizeVolume(volume))
	{
		return;
	}

	std::string allowed_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
		"abcdefghijklmnopqrstuvwxyz:";
	
	for (size_t i = 0; i < volume.size(); ++i)
	{
		if (allowed_chars.find(volume[i]) == std::string::npos)
		{
			return;
		}
	}

	if (volume.size() == 1)
	{
		volume += ":";
	}

	if (install)
	{
		std::string crash_persistent = crashPersistentCbtIsEnabled(std::string(), volume) ? "crash-persistent" : "not-crash-persistent";

		if (reengage)
		{
			system(("urbctctl.exe reengage " + volume + " " + crash_persistent).c_str());
		}
		else
		{
			system(("urbctctl.exe install " + volume + " " + crash_persistent).c_str());
		}
	}
	else
	{
		system(("urbctctl.exe uninstall " + volume).c_str());
	}
#endif
}

void IndexThread::updateCbt()
{
#ifdef _WIN32
	if (os_get_file_type("urbctctl.exe")==0)
	{
		return;
	}

	std::set<std::string> vols;

	std::string settings_fn = "urbackup/data/settings.cfg";
	std::unique_ptr<ISettingsReader> curr_settings(Server->createFileSettingsReader(settings_fn));
	std::string volumes;
	if (curr_settings.get() != NULL)
	{
		if (curr_settings->getValue("image_letters", &volumes)
			|| curr_settings->getValue("image_letters_def", &volumes))
		{
			if (strlower(volumes) == "all")
			{
				volumes = get_all_volumes_list(false, volumes_cache);
			}
			else if (strlower(volumes) == "all_nonusb")
			{
				volumes = get_all_volumes_list(true, volumes_cache);
			}
			
			std::vector<std::string> ret;
			Tokenize(volumes, ret, ";,");
			for (size_t i = 0; i<ret.size(); ++i)
			{
				std::string cvol = trim(ret[i]);
				if (!normalizeVolume(cvol))
					continue;
				cvol = strlower(cvol);

				if (vols.find(cvol) == vols.end())
				{
					enableCbtVol(cvol, cbtIsEnabled(std::string(), cvol), false);
					vols.insert(cvol);
				}
			}
		}
	}

	readBackupDirs();

	for (size_t i = 0; i < backup_dirs.size(); ++i)
	{
		if (backup_dirs[i].facet != index_facet_id)
			continue;

		std::string cvol = trim(backup_dirs[i].path);
		if (!normalizeVolume(cvol))
			continue;

		cvol = strlower(cvol);

		if (!cvol.empty()
			&& vols.find(cvol) == vols.end() )
		{
			enableCbtVol(cvol, cbtIsEnabled(std::string(), cvol), false);
			vols.insert(cvol);
		}
	}

	if ((curr_settings.get() == NULL || volumes.empty())
		&& backup_dirs.empty())
	{
		volumes = get_all_volumes_list(true, volumes_cache);

		std::vector<std::string> ret;
		Tokenize(volumes, ret, ";,");
		for (size_t i = 0; i<ret.size(); ++i)
		{
			std::string cvol = trim(ret[i]);
			if (!normalizeVolume(cvol))
				continue;
			cvol = strlower(cvol);

			if (vols.find(cvol) == vols.end())
			{
				enableCbtVol(cvol, cbtIsEnabled(std::string(), cvol), false);
				vols.insert(cvol);
			}
		}
	}
#endif
}

void IndexThread::createMd5sumsFile(const std::string & path, std::string vol)
{
	normalizeVolume(vol);

	std::string fn = "md5sums-"+conv_filename(vol)+"-"+db->Read("SELECT strftime('%Y-%m-%d %H-%M', 'now', 'localtime') AS fn")[0]["fn"]+".txt";
	std::unique_ptr<IFile> output_f(Server->openFile(fn, MODE_WRITE));

	if (output_f.get() == nullptr)
	{
		Server->Log("Error opening md5sums file. " + os_last_error_str(), LL_ERROR);
	}

	createMd5sumsFile(path, std::string(), output_f.get());
}

void IndexThread::createMd5sumsFile(const std::string & path, const std::string& md5sums_path, IFile * output_f)
{
	std::vector<SFile> files = getFiles(os_file_prefix(path));
	
	for (size_t i = 0; i < files.size(); ++i)
	{
		if (files[i].isdir
			&& !files[i].issym)
		{
			createMd5sumsFile(path + os_file_sep() + files[i].name,
				md5sums_path.empty() ? files[i].name : (md5sums_path + "/" + files[i].name),
				output_f);
		}
		else if (!files[i].isspecialf)
		{
			std::unique_ptr<IFile> f(Server->openFile(os_file_prefix(path + os_file_sep() + files[i].name), MODE_READ_SEQUENTIAL_BACKUP));

			if (f.get() == nullptr)
			{
				Server->Log("Error opening file \"" + path + os_file_sep() + files[i].name + "\" for creating md5sums. " + os_last_error_str(), LL_ERROR);
			}
			else
			{
				MD5 md5;
				std::vector<char> buf;
				buf.resize(32768);

				bool has_read_error = false;
				_u32 rc;
				while ((rc=f->Read(buf.data(), static_cast<_u32>(buf.size()), &has_read_error)) > 0)
				{
					md5.update(reinterpret_cast<unsigned char*>(buf.data()), static_cast<_u32>(rc));
				}

				if (has_read_error)
				{
					Server->Log("Error while reading from file \"" + path + os_file_sep() + files[i].name + "\" for creating md5sums. " + os_last_error_str(), LL_ERROR);
				}

				md5.finalize();

				std::string hex_dig = md5.hex_digest();
				
				output_f->Write(hex_dig + "  " + (md5sums_path.empty() ? files[i].name : (md5sums_path + "/" + files[i].name)) + "\n");
			}
		}
	}
}

void IndexThread::setFlags( unsigned int flags )
{
	calculate_filehashes_on_client = (flags & flag_calc_checksums)>0;
	end_to_end_file_backup_verification = (flags & flag_end_to_end_verification)>0;
	with_scripts = (flags & flag_with_scripts)>0;
	with_orig_path = (flags & flag_with_orig_path)>0;
	with_sequence = (flags & flag_with_sequence)>0;
	with_proper_symlinks = (flags & flag_with_proper_symlinks)>0;
}

bool IndexThread::getAbsSymlinkTarget( const std::string& symlink, const std::string& orig_path,
	std::string& target, std::string& output_target,
	const std::vector<std::string>& exclude_dirs,
	const std::vector<SIndexInclude>& include_dirs)
{
	if (target.empty())
	{
		if (!os_get_symlink_target(symlink, target))
		{
			if (!(index_flags & EBackupDirFlag_SymlinksOptional) && (index_flags & EBackupDirFlag_FollowSymlinks))
			{
				VSSLog("Error getting symlink target of symlink " + symlink + ". Not following symlink.", LL_WARNING);
			}
			return false;
		}

		if (!os_path_absolute(target))
		{
			target = orig_path + os_file_sep() + target;
		}

		target = os_get_final_path(target);
	}

	std::string lower_target;
#ifdef _WIN32
	lower_target = strlower(target);
#else
	lower_target = target;
#endif

#ifndef _WIN32
	if(target=="/"
		&& symlink.find("/.wine/dosdevices/z:")==symlink.size()-20
		&& !FileExists(SYSCONFDIR "/urbackup/follow_wine_link") )
	{
		VSSLog("Not following \"" + symlink + "\" to new symlink backup target at \"" 
			+ target + "\" (Special wine exception. Override via running \"touch " 
			SYSCONFDIR "/urbackup/follow_wine_link\" on the client)", LL_INFO);
		return false;
	}
#endif

	for(size_t i=0;i<backup_dirs.size();++i)
	{
		if (backup_dirs[i].facet != index_facet_id)
			continue;

		if(backup_dirs[i].group!=index_group)
			continue;

		if(backup_dirs[i].symlinked
			&& !(index_flags & EBackupDirFlag_FollowSymlinks))
			continue;

		std::string bpath = addDirectorySeparatorAtEnd(backup_dirs[i].path);
		std::string bpath_wo_slash;
		
		#ifndef _WIN32
		bpath_wo_slash = removeDirectorySeparatorAtEnd(bpath);
		if(bpath.empty())
		{
			bpath="/";
		}
		#else
		bpath = strlower(bpath);
		bpath_wo_slash = removeDirectorySeparatorAtEnd(bpath);
		#endif
		
		if(removeDirectorySeparatorAtEnd(lower_target) == bpath_wo_slash
			|| next(lower_target, 0, bpath))
		{
			std::string tmp_output_target;
			if (target.size() > bpath.size())
			{
				tmp_output_target = target.substr(bpath.size());
			}
			tmp_output_target = backup_dirs[i].tname + 
				(tmp_output_target.empty() ? "" : (os_file_sep() + removeDirectorySeparatorAtEnd(tmp_output_target)));

			if (skipFile(target, tmp_output_target,
				exclude_dirs, include_dirs))
			{
				continue;
			}

			output_target = tmp_output_target;

			if(backup_dirs[i].symlinked && !backup_dirs[i].symlinked_confirmed)
			{
				VSSLog("Following symbolic link at \""+symlink+"\" to \""+ target +"\" confirms symlink backup target \""+backup_dirs[i].tname+"\" to \""+backup_dirs[i].path+"\"", LL_INFO);
				backup_dirs[i].symlinked_confirmed=true;
			}

			return true;
		}
	}

	if (skipFile(target, std::string(),
		exclude_dirs, include_dirs))
	{
		Server->Log("Not following symlink \"" + symlink + "\" because symlink target at \""+target+"\" is excluded", LL_DEBUG);
		return false;
	}
	
	if(index_flags & EBackupDirFlag_FollowSymlinks)
	{
		VSSLog("Following symbolic link at \""+symlink+"\" to new symlink backup target at \""+target+"\"", LL_INFO);
		addSymlinkBackupDir(target, output_target);
		return true;
	}
	else
	{
		Server->Log("Not following symlink \""+symlink+"\" because of configuration.", LL_DEBUG);
		return false;
	}
}

void IndexThread::addSymlinkBackupDir( const std::string& target, std::string& output_target )
{
	std::string name=".symlink_"+ExtractFileName(target);

	//Make sure it is unique
	if(backupNameInUse(name))
	{
		int n=1;
		std::string add="_"+convert(n);
		while(backupNameInUse(name+add))
		{
			add="_"+convert(++n);
		}
		name+=add;
	}

	output_target = name;

	SBackupDir backup_dir;

	cd->addBackupDir(name, target, index_server_default, index_flags, index_group, 1, index_facet_id);

	backup_dir.id=static_cast<int>(db->getLastInsertID());

#ifdef _WIN32
	if(dwt!=NULL)
	{
		std::string msg="A"+target;
		dwt->getPipe()->Write(msg);
    }
#endif

	
	backup_dir.group=index_group;
	backup_dir.flags=index_flags;
	backup_dir.path=target;
	backup_dir.tname=name;
	backup_dir.symlinked=true;
	backup_dir.symlinked_confirmed=true;
	backup_dir.server_default = index_server_default;

	backup_dirs.push_back(backup_dir);

	shareDir(std::string(), name, target);
}

bool IndexThread::backupNameInUse( const std::string& name )
{
	for(size_t i=0;i<backup_dirs.size();++i)
	{
		if (backup_dirs[i].facet != index_facet_id)
			continue;

		if(backup_dirs[i].tname==name)
		{
			return true;
		}
	}
	return false;
}

void IndexThread::removeUnconfirmedSymlinkDirs(size_t off)
{
	for(size_t i=off;i<backup_dirs.size();)
	{
		if (backup_dirs[i].facet != index_facet_id)
		{
			++i;
			continue;
		}

		if(index_group == backup_dirs[i].group)
		{
			if(backup_dirs[i].symlinked
				&& !backup_dirs[i].symlinked_confirmed)
			{
				VSSLog("Not backing up unconfirmed symbolic link \"" + backup_dirs[i].tname + "\" to \"" + backup_dirs[i].path, LL_INFO);
#ifdef _WIN32
				if(dwt!=NULL)
				{
					std::string msg="D"+backup_dirs[i].path;
					dwt->getPipe()->Write(msg);
				}
#endif

				cd->delBackupDir(backup_dirs[i].id);

				removeDir(starttoken, backup_dirs[i].tname);
				removeDir(std::string(), backup_dirs[i].tname);

				if(filesrv!=nullptr)
				{
					filesrv->removeDir(backup_dirs[i].tname, starttoken);
					filesrv->removeDir(backup_dirs[i].tname, std::string());
				}

				backup_dirs.erase(backup_dirs.begin() + i);

				continue;
			}
			else
			{
				break;
			}
		}
		++i;
	}
}

void IndexThread::filterEncryptedFiles(const std::string & dir, const std::string& orig_dir, std::vector<SFile>& files)
{
	bool has_encrypted = false;
	for (size_t i = 0; i < files.size(); ++i)
	{
		if (files[i].isencrypted)
		{
			has_encrypted = true;
		}
	}

	if (has_encrypted)
	{
		std::vector<SFile> new_files;

		for (size_t i = 0; i < files.size(); ++i)
		{
			if (files[i].isencrypted
				&& files[i].isdir)
			{
				bool has_error = false;
				getFiles(os_file_prefix(dir + os_file_sep() + files[i].name), &has_error);

				if (has_error)
				{
					VSSLog("Not backing up encrypted directory \"" + orig_dir + os_file_sep() + files[i].name + "\" (Cannot read directory contents: " + os_last_error_str() + "). See https://www.urbackup.org/faq.html#windows_efs", LL_WARNING);
				}
				else
				{
					new_files.push_back(files[i]);
				}
			}
			else if (files[i].isencrypted
				&& !files[i].isdir)
			{
#ifdef _WIN32
				HANDLE hFile = CreateFileW(Server->ConvertToWchar(os_file_prefix(dir + os_file_sep() + files[i].name)).c_str(), GENERIC_READ, FILE_SHARE_READ, NULL,
					OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_SEQUENTIAL_SCAN | FILE_FLAG_OPEN_REPARSE_POINT, NULL);

				if (hFile == INVALID_HANDLE_VALUE)
				{
					VSSLog("Not backing up encrypted file \"" + orig_dir + os_file_sep() + files[i].name + "\" (Cannot read file contents: " + os_last_error_str() + "). See https://www.urbackup.org/faq.html#windows_efs", LL_WARNING);
				}
				else
				{
					CloseHandle(hFile);

					new_files.push_back(files[i]);
				}
#else
				new_files.push_back(files[i]);
#endif
			}
			else
			{
				new_files.push_back(files[i]);
			}
		}

		files = new_files;
	}
}

std::vector<SFileAndHash> IndexThread::convertToFileAndHash( const std::string& orig_dir, const std::string& named_path, const std::vector<std::string>& exclude_dirs,
	const std::vector<SIndexInclude>& include_dirs, const std::vector<SFile> files, const std::string& fn_filter)
{
	std::vector<SFileAndHash> ret;
	if(fn_filter.empty())
	{
		ret.resize(files.size());
	}

	for(size_t i=0;i<files.size();++i)
	{
		SFileAndHash* curr;
		if(!fn_filter.empty())
		{
			if(files[i].name==fn_filter)
			{
				ret.resize(1);
				curr=&ret[0];
			}
			else
			{
				continue;
			}			
		}
		else
		{
			curr=&ret[i];
		}
		

		curr->isdir=files[i].isdir;
		curr->change_indicator = getChangeIndicator(files[i]);
		curr->name=files[i].name;
		curr->size=files[i].size;
		curr->issym=files[i].issym;
		curr->isspecialf=files[i].isspecialf;
		curr->nlinks = files[i].nlinks;

		if(curr->issym && with_proper_symlinks
			&& !skipFile(orig_dir + os_file_sep() + files[i].name, named_path + os_file_sep() + files[i].name,
				exclude_dirs, include_dirs) )
		{
			SFileAndHash& curr_ret = fn_filter.empty() ? ret[i] : ret[0];
			if(!getAbsSymlinkTarget(orig_dir+os_file_sep()+files[i].name, orig_dir, 
				curr_ret.symlink_target, curr_ret.output_symlink_target,
				exclude_dirs, include_dirs))
			{
				if(!(index_flags & EBackupDirFlag_SymlinksOptional) && (index_flags & EBackupDirFlag_FollowSymlinks) )
				{
					VSSLog("Error getting symlink target of symlink "+orig_dir+os_file_sep()+files[i].name, LL_ERROR);
				}
			}
		}
	}
	return ret;
}

void IndexThread::handleSymlinks(const std::string& orig_dir, std::string named_path, const std::vector<std::string>& exclude_dirs,
	const std::vector<SIndexInclude>& include_dirs, std::vector<SFileAndHash>& files)
{
	for (size_t i = 0; i < files.size(); ++i)
	{
		if (files[i].issym)
		{
			if (skipFile(orig_dir + os_file_sep() + files[i].name, named_path + os_file_sep() + files[i].name,
				exclude_dirs, include_dirs))
			{
				continue;
			}

			if (!getAbsSymlinkTarget(orig_dir + os_file_sep() + files[i].name, orig_dir, 
				files[i].symlink_target, files[i].output_symlink_target,
				exclude_dirs, include_dirs))
			{
				if (!(index_flags & EBackupDirFlag_SymlinksOptional) && (index_flags & EBackupDirFlag_FollowSymlinks))
				{
					VSSLog("Error getting symlink target of symlink " + orig_dir + os_file_sep() + files[i].name, LL_ERROR);
				}
			}
		}
	}
}

int64 IndexThread::randomChangeIndicator()
{
	int64 rnd = Server->getTimeSeconds() << 32 | Server->getRandomNumber();
	rnd &= ~change_indicator_all_bits;
	return rnd;
}

int64 IndexThread::getChangeIndicator(const std::string& path)
{
	SFile file = getFileMetadata(os_file_prefix(path));
	return getChangeIndicator(file);
}

int64 IndexThread::getChangeIndicator(const SFile & file)
{
	int64 change_indicator;
	if (file.usn == 0)
	{
		change_indicator = file.last_modified;
	}
	else
	{
		change_indicator = file.usn;
	}

	if (file.issym)
	{
		change_indicator |= (change_indicator_symlink_bit | change_indicator_special_bit);
	}
	else if (file.isspecialf)
	{
		change_indicator |= change_indicator_special_bit;
	}
	else
	{
		change_indicator &= ~change_indicator_all_bits;
	}
	return change_indicator;
}

#ifndef _WIN32
namespace
{
	struct FLockFile
	{
		FLockFile(std::string fn, bool perm)
			: fn(fn), perm(perm) {}

		std::string fn;
		bool perm;
	};
}

bool IndexThread::start_shadowcopy_lin( SCDirs * dir, std::string &wpath, bool for_imagebackup, bool * &onlyref, bool* not_configured)
{
	std::string scriptname;
	if(dir->fileserv)
	{
		scriptname="create_filesystem_snapshot";
	}
	else
	{
		scriptname="create_volume_snapshot";
	}

	std::string scriptlocation = get_snapshot_script_location(scriptname, index_clientsubname);

	if (scriptlocation.empty())
	{
		if (not_configured != nullptr)
		{
			*not_configured = true;
		}
		return false;
	}

	GUID ssetid = randomGuid();

	std::string loglines;
	int rc = os_popen(scriptlocation +" "+guidToString(ssetid)+" "+escapeDirParam(dir->ref->target)+" "+
		escapeDirParam(dir->dir)+" "+escapeDirParam(dir->orig_target)
		+ (index_clientsubname.empty()?"":(" " + escapeDirParam(index_clientsubname)))+" 2>&1", loglines);

	if(rc!=0)
	{
		VSSLog("Creating snapshot of \""+dir->orig_target+"\" failed", LL_ERROR);
		VSSLogLines(loglines, LL_ERROR);
		return false;
	}

	std::vector<std::string> lines;
	Tokenize(loglines, lines, "\n");
	std::string snapshot_target;
	std::string cbt_info;
	std::string cbt_file;
	
	std::vector<FLockFile> flock_files;
	for(size_t i=0;i<lines.size();++i)
	{
		std::string line = trim(lines[i]);

		if(next(line, 0, "CBT="))
		{
			cbt_info = line.substr(4);
		}
		else if(next(line, 0, "CBT_FILE="))
		{
			cbt_file = line.substr(9);
		}
		else if(next(line, 0, "SNAPSHOT="))
		{
			snapshot_target = line.substr(9);
		}
		else if (next(line, 0, "FLOCK="))
		{
			flock_files.push_back(FLockFile(line.substr(6), false));
		}
		else if (next(line, 0, "FLOCK_PERM="))
		{
			flock_files.push_back(FLockFile(line.substr(11), true));
		}
		else
		{
			VSSLog(line, LL_INFO);
		}
	}

	if (trim(snapshot_target) == "none")
	{
		if (not_configured != nullptr)
		{
			*not_configured = true;
		}
		return false;
	}

	if(snapshot_target.empty())
	{
		VSSLog("Could not find snapshot target. Please include a snapshot target output in the script (e.g. echo SNAPSHOT=/mnt/snap/xxxx)", LL_ERROR);
		return false;
	}

	for (size_t i = 0; i < flock_files.size(); ++i)
	{
		std::string flock_fp = flock_files[i].fn;
		std::unique_ptr<IFsFile> flock_f(Server->openFile(flock_fp, MODE_RW));
		if (flock_f.get() == nullptr)
		{
			VSSLog("Error opening file to lock: " + flock_fp + ". " + os_last_error_str(), LL_WARNING);
		}
		else
		{
			IFsFile::os_file_handle fd = flock_f->getOsHandle(true);

			struct flock fl = {};
			fl.l_type = F_WRLCK;
			fl.l_whence = SEEK_SET;
			fl.l_start = 0;
			fl.l_len = 0;

			int rc = fcntl(fd, F_SETLK, &fl);

			if (rc != 0)
			{
				VSSLog("Error locking file (F_SETLK) " + flock_fp + ". " + os_last_error_str(), LL_WARNING);
			}

			rc = flock(fd, LOCK_EX);

			if (rc != 0)
			{
				VSSLog("Error locking file (flock) " + flock_fp + ". " + os_last_error_str(), LL_WARNING);
			}

			if (flock_files[i].perm)
				flock_fds_perm.push_back(fd);
			else
				dir->ref->flock_fds.push_back(fd);
		}
	}

	dir->target.erase(0,wpath.size());

	if(dir->target.empty() || dir->target[0]!='/')
	{
		dir->target = "/" + dir->target;
	}

	dir->ref->volpath=(snapshot_target);
	dir->starttime=Server->getTimeSeconds();
	dir->target= removeDirectorySeparatorAtEnd(dir->ref->volpath+ (dir->target.empty()? "" : dir->target));
	if(dir->fileserv)
	{
		shareDir(starttoken, dir->dir, dir->target);
	}
	if(for_imagebackup)
	{
		dir->target=snapshot_target;
	}

	SShadowCopy tsc;
	tsc.vssid=ssetid;
	tsc.ssetid=ssetid;
	tsc.target=dir->orig_target;
	tsc.path=dir->ref->volpath;
	tsc.orig_target=dir->orig_target;
	tsc.filesrv=dir->fileserv;
	tsc.vol=wpath;
	tsc.tname=dir->dir;
	tsc.starttoken=starttoken;
	tsc.clientsubname = index_clientsubname;
	if(for_imagebackup)
	{
		tsc.refs=1;
	}
	dir->ref->save_id=cd->addShadowcopy(tsc);
	dir->ref->ok=true;
	dir->ref->ssetid = ssetid;

	VSSLog("Shadowcopy path: "+tsc.path, LL_DEBUG);

	if(!cbt_info.empty())
	{
		str_map cbt_params;
		ParseParamStrHttp(cbt_info, &cbt_params);

		std::string cbt_type = cbt_params["type"];

		if (cbt_type == "datto"
			&& !cbt_file.empty())
		{
			VSSLog("Using datto change information from " + cbt_file, LL_INFO);
			dir->ref->cbt = true;
			dir->ref->cbt_type = CbtType_Datto;
			dir->ref->cbt_file = cbt_file;
		}
		else if (cbt_type == "era")
		{
			VSSLog("Using era change information from device " + cbt_file, LL_INFO);
			dir->ref->cbt = true;
			dir->ref->cbt_type = CbtType_Era;
			dir->ref->cbt_file = cbt_file;
		}

		if (cbt_params["reset"] == "1")
		{
			VSSLog("Resetting CBT information", LL_INFO);
			dir->ref->cbt = true;
			dir->ref->cbt_file.clear();
		}
	}

	if(onlyref!=nullptr)
	{
		*onlyref=false;
	}
	return true;
}

std::string IndexThread::get_snapshot_script_location(const std::string & name, const std::string& index_clientsubname)
{
	std::string conffile = SYSCONFDIR "/urbackup/snapshot.cfg";
	if (!FileExists(conffile))
	{
		return std::string();
	}
	std::unique_ptr<ISettingsReader> settings(Server->createFileSettingsReader(conffile));

	std::string ret;
	if (!index_clientsubname.empty()
		&& settings->getValue(conv_filename(index_clientsubname) + "_" + name, &ret))
	{
		return trim(ret);
	}

	if (settings->getValue(name, &ret))
	{
		return trim(ret);
	}

	return std::string();
}

bool IndexThread::get_volumes_mounted_locally()
{
	std::string ret = strlower(get_snapshot_script_location("volumes_mounted_locally", index_clientsubname));

	return ret != "0" && ret != "false" && ret != "no";
}


#endif //!_WIN32

bool IndexThread::isWindowsLocked()
{
#ifdef _WIN32
	return is_win_locked();
#else
	return false;
#endif
}

std::string IndexThread::escapeDirParam( const std::string& dir )
{
	return "\""+greplace("\"", "\\\"", dir)+"\"";
}

int IndexThread::getShadowId(const std::string & volume, IFile* hdat_img)
{
	IScopedLock lock(cbt_shadow_id_mutex);

	std::map<std::string, int>::iterator it = cbt_shadow_ids.find(strlower(volume));

	if (it != cbt_shadow_ids.end())
	{
		return it->second;
	}
	else
	{
		if (hdat_img != nullptr)
		{
			int shadow_id;
			if (hdat_img->Read(0, reinterpret_cast<char*>(&shadow_id), sizeof(shadow_id)) == sizeof(shadow_id))
			{
				cbt_shadow_ids[strlower(volume)] = shadow_id;
				return shadow_id;
			}
		}

		return -1;
	}
}

#ifndef _WIN32
std::string IndexThread::lookup_shadowcopy(int sid)
{
	std::vector<SShadowCopy> scs = cd->getShadowcopies();
	for (size_t i = 0; i < scs.size(); ++i)
	{
		if (scs[i].id == sid)
		{
			return scs[i].path;
		}
	}
	return std::string();
}

void IndexThread::clearContext(SShadowCopyContext& context)
{
}
#endif

void IndexThread::addScRefs(VSS_ID ssetid, std::vector<SCRef*>& out)
{
	for (size_t i = 0; i < sc_refs.size(); ++i)
	{
		if (sc_refs[i]->ssetid == ssetid)
		{
			out.push_back(sc_refs[i]);
		}
	}
}

void IndexThread::openCbtHdatFile(SCRef* ref, const std::string& sharename, const std::string & volume)
{
	if (ref!=nullptr
		&& ref->cbt)
	{
#ifdef _WIN32
		std::string vol = volume;
		normalizeVolume(vol);
		vol = strlower(vol);
#else
		std::string vol = getMountDevice(volume);
		if(vol.empty())
		{
			return;
		}
#endif

		index_hdat_file.reset(Server->openFile("urbackup/hdat_file_" + conv_filename(vol) + ".dat", MODE_RW_CREATE_DELETE));
		index_hdat_fs_block_size = -1;

#ifdef _WIN32
		DWORD sectors_per_cluster;
		DWORD bytes_per_sector;
		BOOL b = GetDiskFreeSpaceW((Server->ConvertToWchar(vol) + L"\\").c_str(),
			&sectors_per_cluster, &bytes_per_sector, NULL, NULL);
		if (!b)
		{
			VSSLog("Error in GetDiskFreeSpaceW. " + os_last_error_str(), LL_ERROR);
		}
		else
		{
			index_hdat_fs_block_size = bytes_per_sector * sectors_per_cluster;
		}
#endif
		size_t* seq_id = &index_hdat_sequence_ids[strlower(vol)];
		if (index_hdat_file.get()
			&& filesrv != nullptr
			&& index_hdat_fs_block_size>0)
		{
			IFsFile* f = Server->openFile(index_hdat_file->getFilename(), MODE_RW_DELETE);
			if (f != nullptr)
			{
				filesrv->setCbtHashFile(starttoken + "|" + sharename, std::string(),
					IFileServ::CbtHashFileInfo(f, index_hdat_fs_block_size, seq_id, *seq_id));
			}
		}

		client_hash.reset(new ClientHash(index_hdat_file.get(), false, index_hdat_fs_block_size,
			seq_id, *seq_id));

		if (phash_queue != nullptr)
		{
			IFsFile* f = Server->openFile(index_hdat_file->getFilename(), MODE_RW_DELETE);
			if (f != nullptr)
			{
				CWData data;
				data.addChar(ID_CBT_DATA);
				data.addVoidPtr(f);
				data.addVarInt(index_hdat_fs_block_size);
				data.addVoidPtr(seq_id);
				data.addVarInt(*seq_id);

				if (!addToPhashQueue(data))
				{
					Server->destroy(f);
				}
			}
			else
			{
				CWData data;
				data.addChar(ID_INIT_HASH);
				addToPhashQueue(data);
			}
		}
	}
	else
	{
		index_hdat_file.reset();
		client_hash.reset(new ClientHash(nullptr, false, 0, nullptr, 0));

		if (phash_queue != nullptr)
		{
			CWData data;
			data.addChar(ID_INIT_HASH);
			addToPhashQueue(data);
		}

		std::string vol = volume;
		normalizeVolume(vol);
		vol = strlower(vol);

		std::map<std::string, size_t>::iterator it = index_hdat_sequence_ids.find(strlower(vol));
		if (it != index_hdat_sequence_ids.end())
		{
			++it->second;
		}
	}
}

void IndexThread::readSnapshotGroups()
{
	std::string settings_fn = "urbackup/data/settings.cfg";
	if (!index_clientsubname.empty())
	{
		settings_fn = "urbackup/data/settings_" + conv_filename(index_clientsubname) + ".cfg";
	}

	image_snapshot_groups.clear();
	file_snapshot_groups.clear();

	std::unique_ptr<ISettingsReader> curr_settings(Server->createFileSettingsReader(settings_fn));
	if (curr_settings.get() != nullptr)
	{
		readSnapshotGroup(curr_settings.get(), "image_snapshot_groups", image_snapshot_groups);
		readSnapshotGroup(curr_settings.get(), "file_snapshot_groups", file_snapshot_groups);
	}
}

void IndexThread::readSnapshotGroup(ISettingsReader *curr_settings, const std::string & settings_name, std::vector<std::vector<std::string> >& groups)
{
	bool has_volumes = false;
	std::string volumes_str;
	std::vector<std::string> volumes;
#ifdef _WIN32
	if (settings_name == "image_snapshot_groups")
	{
		if (curr_settings->getValue("image_letters", &volumes_str)
			|| curr_settings->getValue("image_letters_def", &volumes_str))
		{
			if (strlower(volumes_str) == "all")
			{
				volumes_str = get_all_volumes_list(false, volumes_cache);
			}
			else if (strlower(volumes_str) == "all_nonusb")
			{
				volumes_str = get_all_volumes_list(true, volumes_cache);
			}

			Tokenize(volumes_str, volumes, ";,");
			for (size_t i = 0; i < volumes.size(); ++i)
			{
				normalizeVolume(volumes[i]);
				volumes[i] = strlower(volumes[i]);
			}
			has_volumes = true;
		}
	}
#endif

	std::string val;
	if (curr_settings->getValue(settings_name, &val) || curr_settings->getValue(settings_name + "_def", &val))
	{
		if (trim(strlower(val)) == "all")
		{
			if (settings_name == "image_snapshot_groups")
			{
				groups.push_back(volumes);
				return;
			}
			else if (settings_name=="file_snapshot_groups")
			{
				std::vector<std::string> groups_mem;
				for (size_t i = 0; i < backup_dirs.size(); ++i)
				{
					if (backup_dirs[i].facet != index_facet_id)
						continue;

					if (backup_dirs[i].group == index_group)
					{
						std::string vol = backup_dirs[i].path;
						normalizeVolume(vol);

#ifdef _WIN32
						vol = strlower(vol);
#endif

						if (std::find(groups_mem.begin(), groups_mem.end(), vol) == groups_mem.end())
						{
							groups_mem.push_back(vol);
						}
					}
				}

				groups.push_back(groups_mem);
				return;
			}
		}

		std::vector<std::string> groups_str;
		Tokenize(val, groups_str, "|");

		for (size_t i = 0; i < groups_str.size(); ++i)
		{
			std::vector<std::string> groups_mem;
			Tokenize(groups_str[i], groups_mem, ";,");

			if (!groups_mem.empty())
			{
				for (size_t j = 0; j < groups_mem.size();)
				{
					normalizeVolume(groups_mem[j]);
#ifdef _WIN32
					groups_mem[j] = strlower(groups_mem[j]);
#endif
					if (has_volumes
						&& std::find(volumes.begin(), volumes.end(), groups_mem[j])
							== volumes.end())
					{
						groups_mem.erase(groups_mem.begin() + j);
						continue;
					}

					++j;
				}

				groups.push_back(groups_mem);
			}
		}
	}
}

std::vector<std::string> IndexThread::getSnapshotGroup(std::string volume, bool for_image)
{
	if (!normalizeVolume(volume))
	{
		return std::vector<std::string>();
	}

#ifdef _WIN32
	volume = strlower(volume);
#endif

	std::vector< std::vector<std::string> >& groups = for_image ? image_snapshot_groups : file_snapshot_groups;

	for (size_t i = 0; i < groups.size(); ++i)
	{
		if (std::find(groups[i].begin(), groups[i].end(), volume) != groups[i].end())
		{


			return groups[i];
		}
	}

	return std::vector<std::string>();
}

std::string IndexThread::otherVolumeInfo(SCDirs * dir, bool onlyref)
{
	if (onlyref
		|| dir->ref==nullptr)
	{
		return std::string();
	}

	std::string other_vols;
	for (size_t i = 0; i < sc_refs.size(); ++i)
	{
		if (sc_refs[i]->volid != dir->ref->volid
			&& sc_refs[i]->ssetid == dir->ref->ssetid)
		{
			other_vols += "&vol_" + convert(i) + "=" + EscapeParamString(sc_refs[i]->target);
			other_vols += "&id_" + convert(i) + "=" + convert(sc_refs[i]->save_id);
		}
	}

	if (!other_vols.empty())
	{
		other_vols[0] = '|';
	}

	return other_vols;
}

void IndexThread::postSnapshotProcessing(SCDirs* scd, bool full_backup)
{
	if (!full_backup)
	{
#ifdef _WIN32
		DirectoryWatcherThread::update_and_wait(open_files);
#endif
		std::sort(open_files.begin(), open_files.end());
	}

	if (scd!=nullptr 
		&& scd->ref != nullptr)
	{
		for (size_t k = 0; k < sc_refs.size(); ++k)
		{
			if (sc_refs[k]->ssetid == scd->ref->ssetid)
			{
				if (sc_refs[k]->cbt)
				{
					sc_refs[k]->cbt = finishCbt(sc_refs[k]->target, -1, sc_refs[k]->volpath, false, sc_refs[k]->cbt_file, sc_refs[k]->cbt_type);
				}

				postSnapshotProcessing(sc_refs[k], full_backup);
			}
		}
	}	
}

void IndexThread::postSnapshotProcessing(SCRef * ref, bool full_backup)
{
	if (full_backup)
	{
		return;
	}

#ifdef _WIN32
	std::string volpath = removeDirectorySeparatorAtEnd(getVolPath(ref->target));

	volpath = strlower(volpath);
#else
	std::string volpath = ref->target;
#endif

	if (volpath.empty())
	{
		VSSLog("Error getting volume path for " + ref->target, LL_WARNING);
	}

	std::vector<int> db_tgroup;
	for (size_t i = 0; i < backup_dirs.size(); ++i)
	{
		if (backup_dirs[i].facet != index_facet_id)
			continue;

		std::string path = backup_dirs[i].path;
#ifdef _WIN32
		path = strlower(path);
#endif
		if (backup_dirs[i].group == index_group
			&& next(path, 0, volpath))
		{
			int tgroup = (backup_dirs[i].flags & EBackupDirFlag_ShareHashes) ? 0 : (backup_dirs[i].group + 1);;
			if (std::find(db_tgroup.begin(), db_tgroup.end(), tgroup) == db_tgroup.end())
			{
				db_tgroup.push_back(tgroup);
			}
		}
	}

	std::vector<std::string> acd = cd->getChangedDirs(volpath, false);
	for (size_t j = 0; j < acd.size(); ++j)
	{
		if (!std::binary_search(changed_dirs.begin(), changed_dirs.end(), acd[j]))
		{
			changed_dirs.push_back(acd[j]);
		}
	}
	std::sort(changed_dirs.begin(), changed_dirs.end());

	VSSLog("Removing deleted directories from index for \"" + volpath + "\"...", LL_DEBUG);
	std::vector<std::string> deldirs = cd->getDelDirs(volpath, false);
	{
		DBScopedWriteTransaction write_transaction(db);
		for (size_t j = 0; j < deldirs.size(); ++j)
		{
			for (size_t i = 0; i < db_tgroup.size(); ++i)
			{
				cd->removeDeletedDir(deldirs[j], db_tgroup[i]);
			}
		}
	}

	VSSLog("Scanning for changed hard links on volume of \"" + ref->target + "\"...", LL_INFO);
	handleHardLinks(ref->target, ref->volpath, volpath);
}

void IndexThread::initParallelHashing(const std::string & async_ticket)
{
	if (phash_queue != nullptr
		&& phash_queue->deref())
	{
		delete phash_queue;
	}

	std::string settings_fn = "urbackup/data/settings.cfg";

	if (!index_clientsubname.empty())
	{
		settings_fn = "urbackup/data/settings_" + conv_filename(index_clientsubname) + ".cfg";
	}

	std::unique_ptr<ISettingsReader> curr_settings(Server->createFileSettingsReader(settings_fn));
	size_t client_hash_threads = 1;
	if (curr_settings.get() != nullptr)
	{
		client_hash_threads = curr_settings->getValue("client_hash_threads", 1);
	}

	if (client_hash_threads > 0)
		--client_hash_threads;

	std::string fn = "phash_" + bytesToHex(async_ticket);
	phash_queue = new SQueueRef(Server->openTemporaryFile(), this, fn);
	phash_queue->ref();
	phash_queue_write_pos = 0;
	os_create_dir(Server->getServerWorkingDir() + "urbackup" + os_file_sep() + "phash");
	filesrv->shareDir("phash_{9c28ff72-5a74-487b-b5e1-8f1c96cd0cf4}", Server->getServerWorkingDir() + "/urbackup/phash", std::string(), true);
	ParallelHash* phash = new ParallelHash(phash_queue->ref(), sha_version, client_hash_threads);
	filesrv->registerScriptPipeFile(fn, phash);
}

bool IndexThread::addToPhashQueue(CWData & data)
{
	_u32 msgsize = static_cast<_u32>(data.getDataSize());
	phash_queue_buffer.insert(phash_queue_buffer.end(), reinterpret_cast<char*>(&msgsize), reinterpret_cast<char*>(&msgsize) + sizeof(msgsize));
	phash_queue_buffer.insert(phash_queue_buffer.end(), data.getDataPtr(), data.getDataPtr() + data.getDataSize());
	return true;
}

bool IndexThread::commitPhashQueue()
{
	if (phash_queue == nullptr
		|| phash_queue->phash_queue==nullptr)
		return true;

	bool ret = phash_queue->phash_queue->Write(phash_queue_write_pos, phash_queue_buffer.data(), static_cast<_u32>(phash_queue_buffer.size())) == phash_queue_buffer.size();
	if (ret)
	{
		phash_queue_write_pos += phash_queue_buffer.size();
	}
	phash_queue_buffer.clear();
	return ret;
}

bool IndexThread::isAllSpecialDir(const SBackupDir& bdir)
{
	if (bdir.path == "ALL"
		&& bdir.tname == "ALL"
		&& bdir.server_default == EBackupDirServerDefault_Default)
	{
		return true;
	}

	if (bdir.path == "ALL_NONUSB"
		&& bdir.tname == "ALL_NONUSB"
		&& bdir.server_default == EBackupDirServerDefault_Default)
	{
		return true;
	}
#ifndef _WIN32
	if (bdir.path == "ALL_NONET"
		&& bdir.tname == "ALL_NONET"
		&& bdir.server_default == EBackupDirServerDefault_Default)
	{
		return true;
	}

	if (bdir.path == "ALL_WITH_TMPFS"
		&& bdir.tname == "ALL_WITH_TMPFS"
		&& bdir.server_default == EBackupDirServerDefault_Default)
	{
		return true;
	}
#endif

	return false;
}
