/*
 * Copyright (c) 2005-2006 Tom N Harris.
 *
 *     This program is free software; you can redistribute it and/or modify
 *     it under the terms of the GNU General Public License as published by
 *     the Free Software Foundation; either version 2 of the License, or
 *     (at your option) any later version.
 *
 *     A copy of the GNU General Public License can be found at
 *     http://www.gnu.org/
 *
 * Written by Tom N Harris <telliamed@whoopdedo.org>.
 *
 */

#include "Backup.h"

#include <string>

#include "zutil.h"

using namespace Setup;
using namespace zLib;
using namespace std;



Backup::Backup(String const& name)
	: m_prefix(name), m_archive(NULL)
{
}

Backup::~Backup()
{
	Close();
}

void 
Backup::Close(void)
{
	if (m_archive)
	{
		m_archive->Close();
		delete m_archive;
	}
}

bool 
Backup::SetDest(String const& dest)
{
	String path = dest;
	replace(path.begin(), path.end(), TEXT('/'), TEXT('\\'));
	if (*path.end() != TEXT('\\'))
		path += TEXT('\\');

	if (m_dir == path && ! m_filename.empty())
		return true;
	
	String basename = path + m_prefix;
	String filename;
	TCHAR datestr[12];
	TCHAR ixstr[4];
	int ix = 0;
	::GetDateFormat(0, 0, NULL, TEXT("yyyyMMdd"), datestr, sizeof(datestr));
	basename += datestr;
	filename = basename + TEXT(".zip");
	while (ix < 100)
	{
		DWORD attr = ::GetFileAttributes(filename.c_str());
		if (attr == INVALID_FILE_ATTRIBUTES)
			break;
		++ix;
		wsprintf(ixstr, TEXT("-%02d"), ix);
		filename = basename + ixstr + TEXT(".zip");
	}
	if (ix >= 100)
	{
		::SetLastError(ERROR_NO_MORE_FILES);
		return false;
	}

	m_dir = path;
	m_filename = filename;
	return true;
}

bool 
Backup::AddFile(String const& fname)
{
	int err = 0;
	if (! m_archive)
	{
		if (m_filename.empty())
		{
			::SetLastError(ERROR_INVALID_NAME);
			return false;
		}
		m_archive = new ZipArchive();
		err = m_archive->Open(m_filename.c_str());
		if (err != ZIP_OK)
		{
			delete m_archive;
			m_archive = NULL;
			::SetLastError(zerr_to_win(err));
			return false;
		}
	}

	string zfname;
	String path = m_dir;
	{
		String fn = fname;
		replace(fn.begin(), fn.end(), TEXT('/'), TEXT('\\'));
		size_t pos = 0;
		while (fn[pos] == TEXT('\\') && ++pos < fn.size()) ;
		if (pos >= fn.size())
			return true;
		fn.erase(0, pos);
		path.append(fn);
#ifdef UNICODE
		zfname = Win32::AnsiString(fn.c_str(), fn.size());
#else
		zfname = fn;
#endif
	}

	HANDLE fh = ::CreateFile(path.c_str(), GENERIC_READ, 0, NULL, 
			OPEN_EXISTING, (Win32::IsNT()) ? FILE_FLAG_BACKUP_SEMANTICS : 0,
			NULL);
	if (fh == INVALID_HANDLE_VALUE)
		return false;

	FILETIME mtime, atime, ctime;
	DWORD attr = GetFileInfo(fh, &mtime, &atime, &ctime);

	unsigned long sdlength;
	char* sd = GetFileSD(path, &sdlength);

	unsigned long zftime = ft_to_dos(&mtime);

	err = m_archive->AddFile(zfname.c_str(), zftime, attr);
	if (err != ZIP_OK)
	{
		::CloseHandle(fh);
		::SetLastError(zerr_to_win(err));
		return false;
	}

	ZipFileTimes(&mtime, &atime, &ctime);
	if (sd)
	{
		ZipFileSD(sd, sdlength);
		delete[] sd;
	}

	char readbuf[1024];
	DWORD nbytes = 0;
	while (1)
	{
		if (! ::ReadFile(fh, readbuf, sizeof(readbuf), &nbytes, NULL))
		{
			err = ::GetLastError();
			break;
		}
		if (nbytes == 0)
			break;
		err = m_archive->Write(readbuf, nbytes);
		if (err != ZIP_OK)
		{
			err = zerr_to_win(err);
			break;
		}
	}

	m_archive->CloseFile();
	::CloseHandle(fh);

	if (err != 0)
	{
		::SetLastError(err);
		return false;
	}
	return true;
}

DWORD 
Backup::GetFileInfo(HANDLE fh, LPFILETIME mt, LPFILETIME at, LPFILETIME ct)
{
	BY_HANDLE_FILE_INFORMATION fi;
	memset(&fi, 0, sizeof(fi));
	if (! ::GetFileInformationByHandle(fh, &fi))
		return 0;

	*mt = fi.ftLastWriteTime;
	*at = fi.ftLastAccessTime;
	*ct = fi.ftCreationTime;

	return fi.dwFileAttributes;
}

void 
Backup::ZipFileTimes(LPFILETIME mt, LPFILETIME at, LPFILETIME ct)
{
	ut_struct utblock;
	ut_cid_struct utcidblock;
	nt_time ntblock;
	memset(&ntblock, 0, sizeof(ntblock));
	memset(&utcidblock, 0, sizeof(utcidblock));
	memset(&utblock, 0, sizeof(utblock));

	ntblock.attr_tag = 1;
	ntblock.attr_size = 24;
	ntblock.mtime = *mt;
	ntblock.atime = *at;
	ntblock.ctime = *ct;

	utblock.flags = 1;
	utblock.mtime = ft_to_unix(mt);
	if (at->dwLowDateTime != 0 && at->dwHighDateTime != 0)
	{
		utblock.flags |= 2;
		utblock.atime = ft_to_unix(at);
	}
	if (ct->dwLowDateTime != 0 && ct->dwHighDateTime != 0)
	{
		utblock.flags |= 4;
		utblock.ctime = ft_to_unix(ct);
	}
	utcidblock.flags = 1;
	utcidblock.mtime = utblock.mtime;

	m_archive->AddExtraField(0x000A, reinterpret_cast<char*>(&ntblock), sizeof(ntblock), NULL, 0);
	m_archive->AddExtraField(0x5455, reinterpret_cast<char*>(&utblock), sizeof(utblock), reinterpret_cast<char*>(&utcidblock), sizeof(utcidblock));
}

char* 
Backup::GetFileSD(String const& fname, unsigned long *plength)
{
	if (! Win32::IsNT())
		return NULL;

	bool success;
	unsigned long length = 1024;
	unsigned long max_len;
	DWORD flags = OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION;

	char *data = new char[length];
	do
	{
		max_len = 0;
		success = ::GetFileSecurity(fname.c_str(), flags, 
				    reinterpret_cast<PSECURITY_DESCRIPTOR>(data), 
				    length, &max_len);
		if (! success)
		{
			delete[] data;
			if (::GetLastError() == ERROR_INSUFFICIENT_BUFFER
			 && max_len < 65536 && length < 65536)
			{
				if (max_len)
					length = max_len + 32;
				else
					length += 1024;

				data = new char[length];
			}
			else
			{
				return NULL;
			}
		}
	}
	while (! success);

	*plength = ::GetSecurityDescriptorLength(reinterpret_cast<PSECURITY_DESCRIPTOR>(data));
	return data;
}

void 
Backup::ZipFileSD(char const* psd, unsigned long length)
{
	unsigned long zlength = ::compressBound(length);
	auto_ptr<char> zdata(new char[zlength + sizeof(sd_struct)]);
	sd_struct *sdblock = reinterpret_cast<sd_struct*>(zdata.get());
	::compress2(reinterpret_cast<Bytef*>(sdblock->sd), &zlength, 
			    reinterpret_cast<const Bytef*>(psd), length, 6);
	sdblock->sd_size = length;
	sdblock->vers = 0;
	sdblock->method = 8;
	sdblock->crc = ::crc32(0, reinterpret_cast<const Bytef*>(psd), length);
	
	m_archive->AddExtraField(0x4453, zdata.get(), zlength+sizeof(sd_struct), 
			    zdata.get(), sizeof(sd_cid_struct));
}


