/*************************************************************************
*    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 "FileMetadataPipe.h"
#include "../common/data.h"
#include <assert.h>
#include "../Interface/Server.h"
#include "../stringtools.h"
#include "../urbackupcommon/os_functions.h"
#include "IFileServ.h"
#include <cstring>
#include "FileServ.h"
#include "PipeSessions.h"
#include "../common/adler32.h"
#include <limits.h>

#ifndef _WIN32
#include <sys/types.h>

#ifndef __FreeBSD__
#include <sys/xattr.h>
#else
#include <sys/extattr.h>
#endif

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#endif

#if defined(__APPLE__)
#define llistxattr(path, list, size) listxattr(path, list, size, XATTR_NOFOLLOW)
#define lgetxattr(path, name, value, size) getxattr(path, name, value, size, 0, XATTR_NOFOLLOW)
#define stat64 stat
#define lstat64 lstat
#endif

#if defined(__FreeBSD__)
#define llistxattr(path, list, size) extattr_list_link(path, EXTATTR_NAMESPACE_USER, list, size)
#define lgetxattr(path, name, value, size) extattr_get_link(path, EXTATTR_NAMESPACE_USER, name, value, size)
#define stat64 stat
#define lstat64 lstat
#endif

const size_t metadata_id_size = 4+4+8+4;


FileMetadataPipe::FileMetadataPipe( IPipe* pipe, const std::string& cmd )
	: PipeFileBase(cmd), pipe(pipe),
#ifdef _WIN32
	hFile(INVALID_HANDLE_VALUE),
	backup_read_state(-1),
#else
	backup_state(BackupState_StatInit),
#endif
	metadata_state(MetadataState_Wait),
		errpipe(Server->createMemoryPipe())
{
	metadata_buffer.resize(4096);
	init();
}

FileMetadataPipe::FileMetadataPipe()
	: PipeFileBase(std::string()), pipe(nullptr),
#ifdef _WIN32
	hFile(INVALID_HANDLE_VALUE),
	backup_read_state(-1),
#else
	backup_state(BackupState_StatInit),
#endif
	metadata_state(MetadataState_Wait),
	errpipe(Server->createMemoryPipe())
{
	metadata_buffer.resize(4096);
}

FileMetadataPipe::~FileMetadataPipe()
{
	assert(token_callback.get() == nullptr);
}


bool FileMetadataPipe::getExitCode( int& exit_code )
{
	exit_code = 0;
	return true;
}

bool FileMetadataPipe::readStdoutIntoBuffer( char* buf, size_t buf_avail, size_t& read_bytes )
{
	if (token_callback.get() == nullptr)
	{
		token_callback.reset(FileServ::newTokenCallback());
	}

	if(buf_avail==0)
	{
		read_bytes = 0;
		return true;
	}

	if(metadata_state==MetadataState_FnSize)
	{
		read_bytes = (std::min)(buf_avail, sizeof(unsigned int) - fn_off);
		unsigned int fn_size = little_endian(static_cast<unsigned int>(public_fn.size()));
		fn_size = little_endian(fn_size);
		memcpy(buf, reinterpret_cast<char*>(&fn_size) + fn_off, read_bytes);
		curr_checksum = urb_adler32(curr_checksum, reinterpret_cast<char*>(&fn_size) + fn_off, static_cast<_u32>(read_bytes));
		fn_off+=read_bytes;

		if(fn_off==sizeof(unsigned int))
		{
			fn_off=0;
			metadata_state = MetadataState_Fn;
		}

		return true;
	}
	else if(metadata_state==MetadataState_Fn)
	{
		read_bytes = (std::min)(buf_avail, public_fn.size()- fn_off);
		memcpy(buf, public_fn.data()+fn_off, read_bytes);
		curr_checksum = urb_adler32(curr_checksum, public_fn.data()+fn_off, static_cast<_u32>(read_bytes));
		fn_off+=read_bytes;

		if(fn_off==public_fn.size())
		{
			fn_off=0;
			metadata_state = MetadataState_FnChecksum;
		}

		return true;
	}
	else if(metadata_state==MetadataState_FnChecksum)
	{
		read_bytes = (std::min)(buf_avail, sizeof(unsigned int) - fn_off);
		unsigned int endian_curr_checksum = little_endian(curr_checksum);
		memcpy(buf, reinterpret_cast<char*>(&endian_curr_checksum) + fn_off, read_bytes);
		fn_off+=read_bytes;

		if(fn_off==sizeof(unsigned int))
		{
			metadata_buffer_size = 0;
			metadata_buffer_off = 0;
			curr_checksum = urb_adler32(0, nullptr, 0);
			if(callback==nullptr)
			{
				metadata_state = MetadataState_Common;
			}
			else
			{
				if(!metadata_file->Seek(metadata_file_off))
				{
					std::string msg = "Error seeking to metadata in \"" + metadata_file->getFilename() + "\"";
					Server->Log(msg, LL_ERROR);
					errpipe->Write(msg);

					read_bytes=0;
					metadata_file.reset();
					PipeSessions::fileMetadataDone(public_fn.substr(1), server_token, active_gen);
					metadata_state = MetadataState_Wait;
					return false;
				}

				metadata_state = MetadataState_File;
			}
		}

		return true;
	}
	else if(metadata_state == MetadataState_Common)
	{
		if(metadata_buffer_size==0)
		{
			SFile file_meta = getFileMetadata(os_file_prefix(local_fn));
			if(file_meta.name.empty())
			{
				Server->Log("Error getting metadata (created and last modified time) of "+local_fn, LL_ERROR);
			}
					
			CWData meta_data;
			meta_data.addChar(1);
#ifdef _WIN32
			meta_data.addVarInt(file_meta.created);
#else
			meta_data.addVarInt(0);
#endif
			meta_data.addVarInt(file_meta.last_modified);
			meta_data.addVarInt(file_meta.accessed);
			meta_data.addVarInt(folder_items);
			meta_data.addVarInt(metadata_id);
			if(token_callback.get()!=nullptr)
			{
				meta_data.addString(token_callback->getFileTokens(local_fn));
			}
			else
			{
				meta_data.addString("");
			}


			size_t meta_size = meta_data.getDataSize() + 2 * sizeof(unsigned int);

			if(meta_size>20*1024*1024)
			{
				Server->Log("File metadata of " + local_fn + " too large (" + convert((size_t)meta_data.getDataSize()) + ")", LL_ERROR);
				return false;
			}
			else if (meta_size > metadata_buffer.size())
			{
				metadata_buffer.resize(meta_size);
			}

			unsigned int data_size = little_endian(static_cast<unsigned int>(meta_data.getDataSize()));
			memcpy(metadata_buffer.data(), &data_size, sizeof(data_size));
			memcpy(metadata_buffer.data()+sizeof(unsigned int), meta_data.getDataPtr(), meta_data.getDataSize());
			metadata_buffer_size = meta_data.getDataSize()+sizeof(unsigned int);

			curr_checksum = urb_adler32(curr_checksum, metadata_buffer.data(), static_cast<_u32>(metadata_buffer_size));
			unsigned int endian_curr_checksum = little_endian(curr_checksum);
			memcpy(metadata_buffer.data()+metadata_buffer_size, &endian_curr_checksum, sizeof(endian_curr_checksum));
			metadata_buffer_size+=sizeof(endian_curr_checksum);
		}

		if (metadata_buffer_size - metadata_buffer_off>0)
		{
			read_bytes = (std::min)(metadata_buffer_size - metadata_buffer_off, buf_avail);
			memcpy(buf, metadata_buffer.data() + metadata_buffer_off, read_bytes);
			metadata_buffer_off += read_bytes;
		}
		if (metadata_buffer_size - metadata_buffer_off == 0)
		{
			metadata_buffer_size = 0;
			metadata_buffer_off = 0;
			curr_checksum = urb_adler32(0, nullptr, 0);
			metadata_state = MetadataState_Os;
		}
		return true;
	}
	else if(metadata_state == MetadataState_Os)
	{
		bool b = transmitCurrMetadata(buf, buf_avail, read_bytes);

		if(read_bytes>0)
		{
			curr_checksum = urb_adler32(curr_checksum, buf, static_cast<_u32>(read_bytes));
		}

		if(!b)
		{
			fn_off=0;
			metadata_state = MetadataState_OsChecksum;

#ifndef _WIN32
			backup_state=BackupState_StatInit;
#else
			hFile = INVALID_HANDLE_VALUE;
			backup_read_state = -1;
#endif

		}
		return true;
	}
	else if(metadata_state == MetadataState_OsChecksum)
	{
		read_bytes = (std::min)(buf_avail, sizeof(unsigned int) - fn_off);
		unsigned int endian_curr_checksum = little_endian(curr_checksum);
		memcpy(buf, reinterpret_cast<char*>(&endian_curr_checksum) + fn_off, read_bytes);
		fn_off+=read_bytes;

		if(fn_off==sizeof(unsigned int))
		{
			PipeSessions::fileMetadataDone(public_fn.substr(1), server_token, active_gen);
			metadata_state = MetadataState_Wait;
		}

		return true;
	}
	else if(metadata_state == MetadataState_File)
	{
		read_bytes = static_cast<size_t>((std::min)(static_cast<int64>(buf_avail), metadata_file_size));

		if(read_bytes==0)
		{
			metadata_state = MetadataState_FileChecksum;
			return true;
		}

		_u32 read = metadata_file->Read(buf, static_cast<_u32>(read_bytes));
		if(read!=read_bytes)
		{
			std::string msg;
			Server->Log(msg, LL_ERROR);
			errpipe->Write(msg);
			memset(buf + read, 0, read_bytes - read);
		}

		curr_checksum = urb_adler32(curr_checksum, buf, static_cast<_u32>(read_bytes));

		metadata_file_size-=read_bytes;

		if(read_bytes<buf_avail)
		{
			fn_off=0;
			metadata_state = MetadataState_File;
		}
		return true;
	}
	else if(metadata_state == MetadataState_FileChecksum)
	{
		read_bytes = (std::min)(buf_avail, sizeof(unsigned int) - fn_off);
		unsigned int endian_curr_checksum = little_endian(curr_checksum);
		memcpy(buf, reinterpret_cast<char*>(&endian_curr_checksum) + fn_off, read_bytes);
		fn_off+=read_bytes;

		if(fn_off==sizeof(unsigned int))
		{
			metadata_file.reset();
			PipeSessions::fileMetadataDone(public_fn.substr(1), server_token, active_gen);
			metadata_state = MetadataState_Wait;
		}

		return true;
	}
	else if (metadata_state == MetadataState_Raw)
	{
		if (raw_metadata.size() - metadata_buffer_off > 0)
		{
			read_bytes = (std::min)(raw_metadata.size() - metadata_buffer_off, buf_avail);
			memcpy(buf, raw_metadata.data() + metadata_buffer_off, read_bytes);
			metadata_buffer_off += read_bytes;
		}
		if (raw_metadata.size() - metadata_buffer_off == 0)
		{
			PipeSessions::fileMetadataDone(public_fn.substr(1), server_token, active_gen);
			metadata_state = MetadataState_Wait;
		}
		return true;
	}
	else if (metadata_state == MetadataState_RawFileFnSize)
	{
		read_bytes = (std::min)(buf_avail, sizeof(unsigned int) - fn_off);
		unsigned int fn_size = little_endian(static_cast<unsigned int>(public_fn.size()));
		fn_size = little_endian(fn_size);
		memcpy(buf, reinterpret_cast<char*>(&fn_size) + fn_off, read_bytes);
		curr_checksum = urb_adler32(curr_checksum, reinterpret_cast<char*>(&fn_size) + fn_off, static_cast<_u32>(read_bytes));
		fn_off += read_bytes;

		if (fn_off == sizeof(unsigned int))
		{
			fn_off = 0;
			metadata_state = MetadataState_RawFileFn;
		}

		return true;
	}
	else if (metadata_state == MetadataState_RawFileFn)
	{
		read_bytes = (std::min)(buf_avail, public_fn.size() - fn_off);
		memcpy(buf, public_fn.data() + fn_off, read_bytes);
		curr_checksum = urb_adler32(curr_checksum, public_fn.data() + fn_off, static_cast<_u32>(read_bytes));
		fn_off += read_bytes;

		if (fn_off == public_fn.size())
		{
			fn_off = 0;
			metadata_state = MetadataState_RawFileFnChecksum;
		}

		return true;
	}
	else if (metadata_state == MetadataState_RawFileFnChecksum)
	{
		read_bytes = (std::min)(buf_avail, sizeof(unsigned int) - fn_off);
		unsigned int endian_curr_checksum = little_endian(curr_checksum);
		memcpy(buf, reinterpret_cast<char*>(&endian_curr_checksum) + fn_off, read_bytes);
		fn_off += read_bytes;

		if (fn_off == sizeof(unsigned int))
		{
			int64* datasize = reinterpret_cast<int64*>(metadata_buffer.data());
			metadata_file_size = transmit_file->Size();
			metadata_file_off = 0;
			*datasize = little_endian(metadata_file_size);
			metadata_buffer_size = sizeof(int64);
			metadata_buffer_off = 0;
			metadata_state = MetadataState_RawFileDataSize;
		}

		return true;
	}
	else if (metadata_state == MetadataState_RawFileDataSize)
	{
		if (metadata_buffer_size - metadata_buffer_off>0)
		{
			read_bytes = (std::min)(metadata_buffer_size - metadata_buffer_off, buf_avail);
			memcpy(buf, metadata_buffer.data() + metadata_buffer_off, read_bytes);
			metadata_buffer_off += read_bytes;
		}
		if (metadata_buffer_size - metadata_buffer_off == 0)
		{
			metadata_buffer_size = 0;
			metadata_buffer_off = 0;
			curr_checksum = urb_adler32(0, nullptr, 0);
			metadata_state = MetadataState_RawFileData;
			sha512_init(&transmit_file_ctx);
		}
		return true;
	}
	else if (metadata_state == MetadataState_RawFileData)
	{
		_u32 max_read = static_cast<_u32>((std::min)(metadata_file_size-metadata_file_off, (std::min)((int64)32768, static_cast<int64>(buf_avail))));

		bool has_read_error = false;
		read_bytes = transmit_file->Read(metadata_file_off, buf, max_read, &has_read_error);

		sha512_update(&transmit_file_ctx, reinterpret_cast<unsigned char*>(buf), static_cast<_u32>(read_bytes));

		if (has_read_error)
		{
			Server->Log("Error reading from file " + transmit_file->getFilename() + ". " + os_last_error_str(), LL_WARNING);
			return false;
		}
		
		metadata_file_off += read_bytes;

		if (metadata_file_off == metadata_file_size)
		{
			metadata_state = MetadataState_RawFileDataChecksum;
			sha512_final(&transmit_file_ctx, reinterpret_cast<unsigned char*>(metadata_buffer.data()));
			metadata_buffer_size = SHA512_DIGEST_SIZE;
			metadata_buffer_off = 0;
		}

		return true;
	}
	else if (metadata_state == MetadataState_RawFileDataChecksum)
	{
		if (metadata_buffer_size - metadata_buffer_off>0)
		{
			read_bytes = (std::min)(metadata_buffer_size - metadata_buffer_off, buf_avail);
			memcpy(buf, metadata_buffer.data() + metadata_buffer_off, read_bytes);
			metadata_buffer_off += read_bytes;
		}
		if (metadata_buffer_size - metadata_buffer_off == 0)
		{
			transmit_wait_pipe->Write(std::string());
			transmit_wait_pipe = nullptr;
			transmit_file = nullptr;
			PipeSessions::fileMetadataDone(public_fn.substr(1), server_token, active_gen);
			metadata_state = MetadataState_Wait;
		}
		return true;
	}


	while(true)
	{
		std::string msg;
		size_t r = pipe->Read(&msg, 60000);

		if(r==0)
		{
			if(pipe->hasError())
			{
				read_bytes = 0;
				return false;
			}
			else
			{
				*buf = ID_METADATA_NOP;
				read_bytes = 1;
				return true;
			}
		}
		else
		{
			CRData msg_data(&msg);

			char id;
			if (msg_data.getChar(&id))
			{
				if (id == METADATA_PIPE_SEND_FILE &&
					msg_data.getStr(&public_fn) &&
					msg_data.getStr(&local_fn) &&
					msg_data.getInt64(&folder_items) &&
					msg_data.getInt64(&metadata_id) &&
					msg_data.getStr(&server_token) &&
					msg_data.getInt64(&active_gen))
				{
					assert(!public_fn.empty() || !local_fn.empty());

					if (std::find(last_public_fns.begin(), last_public_fns.end(), public_fn) != last_public_fns.end())
					{
						PipeSessions::fileMetadataDone(public_fn, server_token, active_gen);
						*buf = ID_METADATA_NOP;
						read_bytes = 1;
						return true;
					}

					last_public_fns.push_back(public_fn);

					if (last_public_fns.size() > 10)
					{
						last_public_fns.pop_front();
					}

					int file_type_flags = os_get_file_type(os_file_prefix(local_fn));

					if (file_type_flags == 0)
					{
						Server->Log("Error getting file type of " + local_fn+". "+os_last_error_str(), LL_ERROR);
						*buf = ID_METADATA_NOP;
						read_bytes = 1;
						PipeSessions::fileMetadataDone(public_fn, server_token, active_gen);
						return true;
					}

					if (!msg_data.getVoidPtr(reinterpret_cast<void**>(&callback)))
					{
						callback = nullptr;
					}

					if (callback==nullptr && !openFileHandle() )
					{
						Server->Log("Error opening file handle to " + local_fn+". "+os_last_error_str(), LL_ERROR);
						*buf = ID_METADATA_NOP;
						read_bytes = 1;
						PipeSessions::fileMetadataDone(public_fn, server_token, active_gen);
						return true;
					}

					std::string file_type;
					if ((file_type_flags & EFileType_Directory)
						&& (file_type_flags & EFileType_Symlink))
					{
						file_type = "l";
					}
					else if (file_type_flags & EFileType_Directory)
					{
						file_type = "d";
					}
					else
					{
						file_type = "f";
					}

					public_fn = file_type + public_fn;

					metadata_state = MetadataState_FnSize;
					*buf = ID_METADATA_V1;
					read_bytes = 1;
					fn_off = 0;
					curr_checksum = urb_adler32(0, nullptr, 0);

					if(callback!=nullptr)
					{
						std::string orig_path;
						_u32 version = 0;
						metadata_file.reset(callback->getMetadata(public_fn, &orig_path, &metadata_file_off, &metadata_file_size, &version, false));

						if (metadata_file.get() == nullptr)
						{
							Server->Log("Error opening metadata file for \"" + public_fn + "\"", LL_ERROR);
							errpipe->Write("Error opening metadata file for \"" + public_fn + "\"");

							*buf = ID_METADATA_NOP;
							read_bytes = 1;
							metadata_state = MetadataState_Wait;
							PipeSessions::fileMetadataDone(public_fn.substr(1), server_token, active_gen);
							return true;
						}

						if (version != 0)
						{
							*buf = version;
						}
						public_fn = file_type + orig_path;
					}

					return true;
				}
				else if (id == METADATA_PIPE_SEND_RAW
					&& msg_data.getStr(&public_fn)
					&& msg_data.getStr(&raw_metadata)
					&& msg_data.getStr(&server_token)
					&& msg_data.getInt64(&active_gen))
				{
					metadata_state = MetadataState_Raw;
					metadata_buffer_off = 0;
					return readStdoutIntoBuffer(buf, buf_avail, read_bytes);
				}
				else if (id == METADATA_PIPE_SEND_RAW_FILEDATA
					&& msg_data.getStr(&public_fn)
					&& msg_data.getVoidPtr(reinterpret_cast<void**>(&transmit_file))
					&& msg_data.getVoidPtr(reinterpret_cast<void**>(&transmit_wait_pipe))
					&& msg_data.getStr(&server_token)
					&& msg_data.getInt64(&active_gen) )
				{
					metadata_state = MetadataState_RawFileFnSize;
					*buf = ID_RAW_FILE;
					read_bytes = 1;
					fn_off = 0;
					curr_checksum = urb_adler32(0, nullptr, 0);

					return true;
				}
				else if (id == METADATA_PIPE_EXIT)
				{
					errpipe->shutdown();
					read_bytes = 0;
					return false;
				}
				else
				{
					Server->Log("Unknown metadata pipe id: " + convert((int)id), LL_ERROR);
					assert(false);
					return false;
				}
			}
			else
			{
				Server->Log("Cannot get metadata pipe id", LL_ERROR);
				assert(false);
				return false;
			}
		}
	}	
}

void FileMetadataPipe::finishStdout()
{
	token_callback.reset();
}

bool FileMetadataPipe::readStderrIntoBuffer( char* buf, size_t buf_avail, size_t& read_bytes )
{
	while(true)
	{
		if(stderr_buf.size()>0)
		{
			read_bytes = (std::min)(buf_avail, stderr_buf.size());
			memcpy(buf, stderr_buf.data(), read_bytes);
			stderr_buf.erase(0, read_bytes);
			return true;
		}

		if(errpipe->Read(&stderr_buf)==0)
		{
            if(errpipe->hasError())
			{
				return false;
			}
		}
	}	
}

void FileMetadataPipe::cleanupOnForceShutdown()
{
	if (metadata_state != MetadataState_Wait)
	{
		PipeSessions::fileMetadataDone(public_fn.substr(1), server_token, active_gen);
	}

	metadata_file.reset();

#ifdef _WIN32
	if (hFile != INVALID_HANDLE_VALUE)
	{
		if (backup_read_context != NULL)
		{
			BackupRead(hFile, NULL, 0, NULL, TRUE, TRUE, &backup_read_context);
			backup_read_context = NULL;
		}

		CloseHandle(hFile);
		hFile = INVALID_HANDLE_VALUE;
	}
#endif

	while (true)
	{
		std::string msg;
		size_t r = pipe->Read(&msg, 0);

		if (r == 0)
		{
			break;
		}

		CRData msg_data(&msg);

		char id;
		if (msg_data.getChar(&id))
		{
			if (id == METADATA_PIPE_SEND_FILE &&
				msg_data.getStr(&public_fn) &&
				msg_data.getStr(&local_fn) &&
				msg_data.getInt64(&folder_items) &&
				msg_data.getInt64(&metadata_id) &&
				msg_data.getStr(&server_token) &&
				msg_data.getInt64(&active_gen) )
			{
				PipeSessions::fileMetadataDone(public_fn, server_token, active_gen);
			}
			else if (id == METADATA_PIPE_SEND_RAW
				&& msg_data.getStr(&public_fn)
				&& msg_data.getStr(&raw_metadata)
				&& msg_data.getStr(&server_token)
				&& msg_data.getInt64(&active_gen))
			{
				PipeSessions::fileMetadataDone(public_fn.substr(1), server_token, active_gen);
			}
			else if (id == METADATA_PIPE_SEND_RAW_FILEDATA
				&& msg_data.getStr(&public_fn)
				&& msg_data.getVoidPtr(reinterpret_cast<void**>(&transmit_file))
				&& msg_data.getVoidPtr(reinterpret_cast<void**>(&transmit_wait_pipe))
				&& msg_data.getStr(&server_token)
				&& msg_data.getInt64(&active_gen))
			{
				PipeSessions::fileMetadataDone(public_fn.substr(1), server_token, active_gen);
			}
		}
	}
}


void FileMetadataPipe::forceExitWait()
{
	CWData data;
	data.addChar(METADATA_PIPE_EXIT);
	pipe->Write(data.getDataPtr(), data.getDataSize());

	errpipe->shutdown();

	waitForExit();
}

IPipe* FileMetadataPipe::getErrPipe()
{
	return errpipe.get();
}

bool FileMetadataPipe::openOsMetadataFile(const std::string& fn)
{
	local_fn = fn;
	return openFileHandle();
}

bool FileMetadataPipe::readCurrOsMetadata(char* buf, size_t buf_avail, size_t& read_bytes)
{
	return transmitCurrMetadata(buf, buf_avail, read_bytes);
}

#ifdef _WIN32
bool FileMetadataPipe::transmitCurrMetadata( char* buf, size_t buf_avail, size_t& read_bytes )
{
	if(metadata_buffer_size-metadata_buffer_off>0)
	{
		read_bytes = (std::min)(metadata_buffer_size-metadata_buffer_off, buf_avail);
		memcpy(buf, metadata_buffer.data()+metadata_buffer_off, read_bytes);
		metadata_buffer_off+=read_bytes;
		return true;
	}

	if(backup_read_state==-1)
	{
		if(hFile==INVALID_HANDLE_VALUE)
		{
			errpipe->Write("Error opening file \""+local_fn+"\" to read metadata. Last error: "+convert((int)GetLastError())+"\n");
			return false;
		}

		backup_read_state = 0;
		backup_read_context=NULL;

		FILE_BASIC_INFO file_information;
		if(GetFileInformationByHandleEx(hFile, FileBasicInfo, &file_information, sizeof(file_information))==FALSE)
		{
			errpipe->Write("Error getting file attributes of \""+local_fn+"\". Last error: "+convert((int)GetLastError())+"\n");
			return false;
		}

		CWData data;
		data.addChar(1);
		data.addUInt(file_information.FileAttributes);
		data.addVarInt(file_information.CreationTime.QuadPart);
		data.addVarInt(file_information.LastAccessTime.QuadPart);
		data.addVarInt(file_information.LastWriteTime.QuadPart);
		data.addVarInt(file_information.ChangeTime.QuadPart);

		_u32 data_size = little_endian(static_cast<_u32>(data.getDataSize()));
		memcpy(metadata_buffer.data(), &data_size, sizeof(data_size));
		memcpy(metadata_buffer.data()+sizeof(data_size), data.getDataPtr(), data.getDataSize());
		metadata_buffer_size = data.getDataSize()+sizeof(data_size);
		metadata_buffer_off=0;
		return transmitCurrMetadata(buf, buf_avail, read_bytes);
	}

	if(backup_read_state==0)
	{
		DWORD total_read = 0;
		DWORD read;
		BOOL b = BackupRead(hFile, reinterpret_cast<LPBYTE>(metadata_buffer.data()+1), metadata_id_size, &read, FALSE, TRUE, &backup_read_context);

		if(b==FALSE)
		{
			errpipe->Write("Error getting metadata of file \""+local_fn+"\". Last error: "+convert((int)GetLastError())+"\n");
			*buf = 0;
			read_bytes = 1;
			return false;
		}

		if(read==0)
		{
			BackupRead(hFile, NULL, 0, NULL, TRUE, TRUE, &backup_read_context);
			backup_read_context=NULL;
			
			CloseHandle(hFile);
			hFile = INVALID_HANDLE_VALUE;
			backup_read_state = -1;

			*buf=0;
			read_bytes = 1;
			return false;
		}

		if(read!=metadata_id_size)
		{
			errpipe->Write("Error getting metadata stream structure.\n");
			*buf=0;
			read_bytes = 1;
			return false;
		}

		total_read += read;

		metadata_buffer[0]=1;

		WIN32_STREAM_ID* curr_stream = reinterpret_cast<WIN32_STREAM_ID*>(metadata_buffer.data()+1);

		if(curr_stream->dwStreamNameSize>0)
		{
			if (3 + read + curr_stream->dwStreamNameSize > metadata_buffer.size())
			{
				metadata_buffer.resize(3 + read + curr_stream->dwStreamNameSize);
			}

			b = BackupRead(hFile, reinterpret_cast<LPBYTE>(metadata_buffer.data()+1) + read, curr_stream->dwStreamNameSize, &read, FALSE, TRUE, &backup_read_context);

			if(b==FALSE)
			{
				errpipe->Write("Error getting metadata of file \""+local_fn+"\" (2). Last error: "+convert((int)GetLastError())+"\n");
				*buf=0;
				read_bytes = 1;
				return false;
			}

			if(read!=curr_stream->dwStreamNameSize)
			{
				errpipe->Write("Error getting metadata stream structure (name).\n");
				*buf=0;
				read_bytes = 1;
				return false;
			}

			curr_stream->cStreamName[curr_stream->dwStreamNameSize/sizeof(WCHAR)] = 0;

			total_read += read;

			stream_name = Server->ConvertFromWchar(curr_stream->cStreamName);
		}
		else
		{
			stream_name.clear();
		}

		if(curr_stream->dwStreamId==BACKUP_DATA
			|| curr_stream->dwStreamId==BACKUP_SPARSE_BLOCK)
		{
			//skip
			LARGE_INTEGER seeked;
			DWORD high_seeked;
			b = BackupSeek(hFile, curr_stream->Size.LowPart, curr_stream->Size.HighPart, &seeked.LowPart, &high_seeked, &backup_read_context);
			seeked.HighPart = high_seeked;

			if(b==FALSE)
			{
				errpipe->Write("Error skipping data stream of file \""+local_fn+"\" (1). Last error: "+convert((int)GetLastError())+"\n");
				*buf=0;
				read_bytes = 1;
				return false;
			}

			if(seeked.QuadPart!=curr_stream->Size.QuadPart)
			{
				errpipe->Write("Error skipping data stream of file \""+local_fn+"\" (2). Last error: "+convert((int)GetLastError())+"\n");
				*buf=0;
				read_bytes = 1;
				return false;
			}

			return transmitCurrMetadata(buf, buf_avail, read_bytes);
		}

		if (curr_stream->Size.QuadPart > 0)
		{
			backup_read_state = 1;
			curr_stream_size = curr_stream->Size;
		}

		metadata_buffer_size = total_read + 1;
		metadata_buffer_off = 0;
		curr_pos = 0;

		return transmitCurrMetadata(buf, buf_avail, read_bytes);
	}
	else if(backup_read_state==1)
	{
		DWORD toread = static_cast<DWORD>((std::min)(curr_stream_size.QuadPart-curr_pos, static_cast<int64>(buf_avail)));

		DWORD read;
		BOOL b = BackupRead(hFile, reinterpret_cast<LPBYTE>(buf), toread, &read, FALSE, TRUE, &backup_read_context);

		if(b==FALSE)
		{
			errpipe->Write("Error reading metadata stream \""+stream_name+"\" of file \""+local_fn+"\". Last error: "+convert((int)GetLastError())+"\n");
			memset(buf, 0, toread);
		}

		read_bytes = read;
		curr_pos+=read;

		assert(curr_pos <= curr_stream_size.QuadPart);

		if(curr_pos==curr_stream_size.QuadPart)
		{
			backup_read_state = 0;
		}

		return true;
	}

	return false;
}

bool FileMetadataPipe::openFileHandle()
{
	backup_read_context = NULL;
	hFile = CreateFileW(Server->ConvertToWchar(os_file_prefix(local_fn)).c_str(), GENERIC_READ | ACCESS_SYSTEM_SECURITY | READ_CONTROL, FILE_SHARE_READ, NULL,
		OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_SEQUENTIAL_SCAN | FILE_FLAG_OPEN_REPARSE_POINT, NULL);

	backup_read_state = -1;

	if (hFile == INVALID_HANDLE_VALUE
		&& next(local_fn, 0, "\\\\?\\UNC\\"))
	{
		hFile = CreateFileW(Server->ConvertToWchar(os_file_prefix(local_fn)).c_str(), GENERIC_READ| READ_CONTROL, FILE_SHARE_READ, NULL,
			OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_SEQUENTIAL_SCAN | FILE_FLAG_OPEN_REPARSE_POINT, NULL);
	}

	return hFile != INVALID_HANDLE_VALUE;
}

#else //_WIN32

void serialize_stat_buf(const struct stat64& buf, const std::string& symlink_target, CWData& data)
{
	data.addChar(1);
	data.addVarInt(buf.st_dev);
	data.addVarInt(buf.st_mode);
	data.addVarInt(buf.st_uid);
	data.addVarInt(buf.st_gid);
	data.addVarInt(buf.st_rdev);
#if defined(__APPLE__) || defined(__FreeBSD__)
	data.addVarInt(buf.st_atimespec.tv_sec);
	data.addUInt(buf.st_atimespec.tv_nsec);
	data.addVarInt(buf.st_mtimespec.tv_sec);
	data.addUInt(buf.st_mtimespec.tv_nsec);
	data.addVarInt(buf.st_ctimespec.tv_sec);
	data.addUInt(buf.st_ctimespec.tv_nsec);
	data.addVarInt(buf.st_flags);
#else
	data.addVarInt(buf.st_atime);
	data.addUInt(buf.st_atim.tv_nsec);
	data.addVarInt(buf.st_mtime);
	data.addUInt(buf.st_mtim.tv_nsec);
	data.addVarInt(buf.st_ctime);
	data.addUInt(buf.st_ctim.tv_nsec);
	data.addVarInt(0);
#endif
	data.addString(symlink_target);
}

namespace
{
    bool get_xattr_keys(const std::string& fn, std::vector<std::string>& keys)
	{
		ssize_t mult = 1;
		while(true)
		{
			ssize_t bufsize;
            bufsize = llistxattr(fn.c_str(), nullptr, 0);

			if(bufsize==-1)
			{
				if (errno == EOPNOTSUPP)
				{
					return true;
				}
#ifdef __APPLE__
				if (errno == EPERM)
				{
					//fs object does not support extended attributes
					return true;
				}
#endif
				Server->Log("Error getting extended attribute list of file "+fn+" errno: "+convert(errno), LL_ERROR);
				return false;
			}

			std::string buf;
			buf.resize(bufsize*mult);

            bufsize = llistxattr(fn.c_str(), &buf[0], buf.size());

			if(bufsize==-1 && errno==ERANGE)
			{
				++mult;
				if (mult >= 10)
				{
					Server->Log("Error getting extended attribute list of file " + fn + " errno: " + convert(errno) + " (3)", LL_ERROR);
					return false;
				}
				Server->Log("Extended attribute list size increased. Retrying...", LL_DEBUG);
				continue;
			}

			if(bufsize==-1)
			{
				Server->Log("Error getting extended attribute list of file "+fn+" errno: "+convert(errno)+" (2)", LL_ERROR);
				return false;
			}

			Tokenize(buf, keys, std::string(1, '\0'));

            for(size_t i=0;i<keys.size();)
            {
				if (keys[i].empty()
					|| keys[i].size()>=UINT_MAX)
				{
					keys.erase(keys.begin() + i);
					continue;
				}

                unsigned int ksize = static_cast<unsigned int>(keys[i].size());
                ksize = little_endian(ksize);

                keys[i].insert(0, reinterpret_cast<char*>(&ksize), sizeof(ksize));

				++i;
            }

			return true;
		}
	}

    bool get_xattr(const std::string& fn, const std::string& key, std::string& value)
	{
		while(true)
		{
			ssize_t bufsize;
            bufsize = lgetxattr(fn.c_str(), key.c_str()+sizeof(unsigned int), nullptr, 0);

			if(bufsize==-1)
			{
				Server->Log("Error getting extended attribute \""+key+"\" of file \""+fn+"\" errno: "+convert(errno), LL_ERROR);
				return false;
			}

			value.resize(bufsize+sizeof(_u32));

            bufsize = lgetxattr(fn.c_str(), key.c_str()+sizeof(unsigned int), &value[sizeof(_u32)], value.size()-sizeof(_u32));

			if(bufsize==-1 && errno==ERANGE)
			{
				Server->Log("Extended attribute size increased. Retrying...", LL_DEBUG);
				continue;
			}

			if(bufsize==-1)
			{
				Server->Log("Error getting extended attribute \""+key+"\" of file \""+fn+"\" errno: "+convert(errno)+" (2)", LL_ERROR);
				return false;
			}

			if(bufsize<value.size()-sizeof(_u32))
			{
				value.resize(bufsize+sizeof(_u32));
			}

			_u32 vsize=static_cast<_u32>(bufsize);
			vsize=little_endian(vsize);

			memcpy(&value[0], &vsize, sizeof(vsize));

			return true;
		}
	}
}

bool FileMetadataPipe::transmitCurrMetadata(char* buf, size_t buf_avail, size_t& read_bytes)
{
	if(backup_state==BackupState_StatInit)
	{
		CWData data;
        struct stat64 statbuf;
        int rc = lstat64(local_fn.c_str(), &statbuf);

		if(rc!=0)
		{
            Server->Log("Error with lstat of "+local_fn+" errorcode: "+convert(errno), LL_ERROR);
			return false;
		}

		std::string symlink_target;
		if (S_ISLNK(statbuf.st_mode))
		{
			if (!os_get_symlink_target(local_fn, symlink_target))
			{
				Server->Log("Error getting symlink target of " + local_fn + " errorcode: " + convert(errno), LL_ERROR);
				return false;
			}
		}

        serialize_stat_buf(statbuf, symlink_target, data);

		if(data.getDataSize()+sizeof(_u32)>metadata_buffer.size())
		{
			Server->Log("File metadata of "+local_fn+" too large ("+convert((size_t)data.getDataSize()+sizeof(_u32))+")", LL_ERROR);
			return false;
		}


		_u32 metadata_size = little_endian(static_cast<_u32>(data.getDataSize()));
		memcpy(metadata_buffer.data(), &metadata_size, sizeof(_u32));
		memcpy(metadata_buffer.data()+sizeof(_u32), data.getDataPtr(), data.getDataSize());
		metadata_buffer_size=data.getDataSize()+sizeof(_u32);
		metadata_buffer_off=0;
		backup_state=BackupState_Stat;

		return transmitCurrMetadata(buf, buf_avail, read_bytes);
	}
    else if(backup_state==BackupState_Stat)
	{
		if(metadata_buffer_size-metadata_buffer_off>0)
		{
			read_bytes = (std::min)(metadata_buffer_size-metadata_buffer_off, buf_avail);
			memcpy(buf, metadata_buffer.data()+metadata_buffer_off, read_bytes);
			metadata_buffer_off+=read_bytes;
		}
		if(metadata_buffer_size-metadata_buffer_off==0)
		{
			backup_state=BackupState_EAttrInit;
		}
		return true;
	}
	else if(backup_state==BackupState_EAttrInit)
	{
		eattr_keys.clear();
        if(!get_xattr_keys(local_fn, eattr_keys))
		{
			return false;
		}

		CWData data;
		data.addInt64(eattr_keys.size());

		memcpy(metadata_buffer.data(), data.getDataPtr(), data.getDataSize());
		metadata_buffer_size=data.getDataSize();
		metadata_buffer_off=0;
		backup_state=BackupState_EAttr;

        return transmitCurrMetadata(buf, buf_avail, read_bytes);
	}
	else if(backup_state==BackupState_EAttr)
	{
		if(metadata_buffer_size-metadata_buffer_off>0)
		{
			read_bytes = (std::min)(metadata_buffer_size-metadata_buffer_off, buf_avail);
			memcpy(buf, metadata_buffer.data()+metadata_buffer_off, read_bytes);
			metadata_buffer_off+=read_bytes;
		}
		if(metadata_buffer_size-metadata_buffer_off==0)
		{
			if(!eattr_keys.empty())
			{
				eattr_idx=0;
				backup_state=BackupState_EAttr_Vals_Key;
				eattr_key_off=0;
			}
			else
			{
				return false;
			}
		}
		return true;
	}
	else if(backup_state==BackupState_EAttr_Vals_Key)
	{
        if(eattr_keys[eattr_idx].size()-eattr_key_off>0)
		{
            read_bytes = (std::min)(eattr_keys[eattr_idx].size()-eattr_key_off, buf_avail);
			memcpy(buf, eattr_keys[eattr_idx].c_str()+eattr_key_off, read_bytes);
			eattr_key_off+=read_bytes;
		}

        if(eattr_keys[eattr_idx].size()-eattr_key_off==0)
		{			
            if(!get_xattr(local_fn, eattr_keys[eattr_idx], eattr_val))
			{
				eattr_val.resize(sizeof(_u32));
				unsigned int umax = UINT_MAX;
				memcpy(&eattr_val[0], &umax, sizeof(umax));
			}
			backup_state = BackupState_EAttr_Vals_Val;
			eattr_val_off=0;
		}
		return true;
	}
	else if (backup_state == BackupState_EAttr_Vals_Val)
	{
		if (eattr_val.size() - eattr_val_off > 0)
		{
			read_bytes = (std::min)(eattr_val.size() - eattr_val_off, buf_avail);
			memcpy(buf, eattr_val.data() + eattr_val_off, read_bytes);
			eattr_val_off += read_bytes;
		}

		if (eattr_val.size() - eattr_val_off == 0)
		{
			if (eattr_idx + 1 < eattr_keys.size())
			{
				++eattr_idx;
				backup_state = BackupState_EAttr_Vals_Key;
				eattr_key_off = 0;
			}
			else
			{
				//finished
				return false;
			}
		}
		return true;
	}

	return false;
}

bool FileMetadataPipe::openFileHandle()
{
	return true;
}

#endif //_WIN32

