// Local configuration
#include "config.h"

// Implementation
#include "QuotaInfo.hpp"

// System C
#ifndef WIN32
#include <mntent.h>   // unix: read mtab
#endif

// Windows DiskQuota
#ifdef W2K8
#include <comutil.h>
#include <fsrmquota.h>
#include <fsrmtlb.h>
#include <boost/regex.hpp>
#endif

#ifdef __linux__
#include <sys/quota.h>   // linux-std
#include <asm/types.h>   // linux-std
#ifdef HAVE_XFS_XQM_H
#include <xfs/xqm.h>     // linux-xfs
#endif
#include <rpc/clnt.h>       // linux-rpc
#include <rpcsvc/rquota.h>  // linux-rpc
#endif

// Local
#include "Text.hpp"

namespace UI {
namespace Util {

std::string QuotaInfo::FS::getMethods(std::string const & fstype) const
{
	std::string const fst(fstype == "" ? getType() : fstype);
	std::string result("");
	Methods::MethodVec const & mv(methods_.getMethods(fst));
	for (Methods::MethodVec::const_iterator m(mv.begin()); m != mv.end(); ++m)
	{
		result += (*m)->first + " ";
	}
	return result;
}

QuotaInfo::FS QuotaInfo::file2fs(std::string const & fName, std::string const & tab)
{
	QuotaInfo::FS res("");

#ifdef WIN32
	if(fName.length() < 3)
	{
		UI_THROW("File name to short; can't extract drive: " + fName);
	}
	res = QuotaInfo::FS(fName.substr(0, 3));
#else
	std::string::size_type resDirOverhang(std::string::npos);
	struct mntent * mnt;
	FILE * fp(setmntent(tab.c_str(), "r"));
	if (!fp)
	{
		UI_THROW("Error opening mount table: " + tab);
	}

	while ((mnt = getmntent(fp)))
	{
		std::string const dir(mnt->mnt_dir);

		if (fName.compare(0, dir.size(), dir) == 0)
		{
			std::string::size_type const overhang(fName.size()-dir.size());
			if ((resDirOverhang == std::string::npos) || (overhang <= resDirOverhang))
			{
				res=QuotaInfo::FS(mnt->mnt_fsname, mnt->mnt_type);
				resDirOverhang=overhang;
			}
		}
	}

	endmntent(fp);
#endif

	return res;
}

QuotaInfo::Methods::Methods()
{
	// Method Map
#ifdef HAVE_SYS_QUOTA_H
	m_["linux-v2"] = &QuotaInfo::methodLinuxV2;
#endif
#ifdef HAVE_XFS_XQM_H
	m_["linux-xfs"] = &QuotaInfo::methodLinuxXfs;
#endif
#ifdef __linux__
	m_["linux-rpc"] = &QuotaInfo::methodLinuxRpc;
#endif
#ifdef HAVE_SYS_QUOTA_H
	m_["linux-v1"] = &QuotaInfo::methodLinuxV1;
#endif
#ifdef WIN32
#ifndef W2K8
	m_["no-quota"] = &QuotaInfo::noQuota;
#else
	m_["w2k8-quota"] = &QuotaInfo::w2k8Quota;
#endif
#endif

	// FS Map
#ifdef __linux__	
	// Default
	f_["__all__"].push_back(m_.find("linux-v2"));
	f_["__all__"].push_back(m_.find("linux-xfs"));
	f_["__all__"].push_back(m_.find("linux-v1"));
	f_["__all__"].push_back(m_.find("linux-rpc"));

	// XFS
	f_["xfs"].push_back(m_.find("linux-xfs"));

	// NFS
	f_["nfs"].push_back(m_.find("linux-rpc"));

	// NFS4
	f_["nfs4"].push_back(m_.find("linux-rpc"));
#endif

#ifdef WIN32
#ifndef W2K8
	f_["__all__"].push_back(m_.find("no-quota"));
#else
	f_["__all__"].push_back((m_.find("w2k8-quota")));
#endif
#endif
}

QuotaInfo::Methods::MethodVec const & QuotaInfo::Methods::getMethods(std::string const & fstype) const
{
	FSMap::const_iterator mv(f_.find(fstype));
	if (mv == methods_.f_.end())
	{
		mv = methods_.f_.find("__all__");
	}
	return mv->second;
}


// No quota method: Limit "0" means no limit
#ifdef WIN32
#ifndef W2K8
void QuotaInfo::noQuota(std::string const & dev, uid_t const uid)
{
	blockHL_=0;
	blockSL_=0;
	blocks_=0;

	inodeHL_=0;
	inodeSL_=0;
	inodes_=0;

	blockTL_=0;
	inodeTL_=0;
}
#else
void QuotaInfo::w2k8Quota(std::string const & dev, uid_t const uid)
{
	if (home_ == "")
	{
		UI_THROW("Home not provided: " + home_);
	}

	// reduce the home path to the customers root path
	// e.g. E:\kunden\homepages\18\d12345678\www\wsb123456\ -> E:\kunden\homepages\18\d12345678
	boost::regex regExNormal("^(e:\\\\kunden\\\\homepages\\\\[0-9][0-9]?\\\\d[0-9]+)(\\\\www\\\\.+)");
	boost::cmatch what;
	if (!boost::regex_match(home_.c_str(), what, regExNormal))
	{
		// may also be e.g. E:\kunden\homepages\18\d12345678\www
		boost::regex regExOtherNormal("^(e:\\\\kunden\\\\homepages\\\\[0-9][0-9]?\\\\d[0-9]+)(\\\\www)");
		if (!boost::regex_match(home_.c_str(), what, regExOtherNormal))
		{
			// may already be without "www"
			boost::regex regExAlreadyCut("^(e:\\\\kunden\\\\homepages\\\\[0-9][0-9]?\\\\d[0-9]+)");
			if (!boost::regex_match(home_.c_str(), what, regExAlreadyCut))
			{
				UI_THROW("File path not matched: " + home_);
			}
		}
	}

	std::string cstrCustomersRoot = what[1];

	HRESULT hr = CoInitializeEx(NULL, 0);
	if (SUCCEEDED(hr))
	{
		IFsrmQuotaManager* pQuotaManager;
		IFsrmQuota* pQuota;

		ULONGLONG ullQuotaLimit = 0;
		ULONGLONG ullQuotaUsed = 0;

		hr = CoCreateInstance(CLSID_FsrmQuotaManager, NULL,
			CLSCTX_LOCAL_SERVER, IID_IFsrmQuotaManager,
			reinterpret_cast<void**>(&pQuotaManager));
		if (SUCCEEDED(hr))
		{
			hr = pQuotaManager->GetQuota(_bstr_t(cstrCustomersRoot.c_str()), &pQuota);
			if (FAILED(hr))
			{
				if (hr == (LONG)0x80045301) //no quota defined, so just go on
				{
					blockHL_=0;
					blockSL_=0;
					blocks_=0;

					inodeHL_=0;
					inodeSL_=0;
					inodes_=0;

					blockTL_=0;
					inodeTL_=0;
				}
			}
			else
			{
				VARIANT var;
				VariantInit(&var);
				hr = pQuota->get_QuotaLimit(&var);
				if (SUCCEEDED(hr))
				{
					ullQuotaLimit = var.ullVal;
				}

				VariantClear(&var);

				hr = pQuota->get_QuotaUsed(&var);
				if (SUCCEEDED(hr))
				{
					ullQuotaUsed = var.ullVal;
				}

				blockHL_ = static_cast<unsigned long>(ullQuotaLimit / BlockSize_);
				blockSL_ = 0;
				blocks_ = static_cast<unsigned long>(ullQuotaUsed / BlockSize_);

				inodeHL_ = 0;
				inodeSL_ = 0;
				inodes_ = 0;

				blockTL_ = 0;
				inodeTL_ = 0;

				VariantClear(&var);

				pQuota->Release();
			}
			pQuotaManager->Release();
		}
		CoUninitialize();
	}
}
#endif
#endif

QuotaInfo::Methods const QuotaInfo::methods_;

QuotaInfo::QuotaInfo(FS const & fs, int const id, Type const type, std::string const & strPath)
#ifndef WIN32
	:sub_(type == Usr_ ? USRQUOTA : GRPQUOTA)
#else
	:sub_(0)
#endif
	,home_(strPath)
	,method_("none")
{
	bool found(false);
	std::string errorText("QuotaInfo failed:");

	Methods::MethodVec const & mv(methods_.getMethods(fs.fstype_));

	for (Methods::MethodVec::const_iterator m(mv.begin()); !found && m != mv.end(); ++m)
	{
		try
		{
			(this->*(*m)->second)(fs.dev_, id);
			method_=(*m)->first;
			found=true;
		}
		catch (std::exception const & e)
		{
			errorText+=" Method " + (*m)->first + " said: " + e.what() + ".";
		}
	}
	if (!found)
	{
		UI_THROW(errorText);
	}
}

#ifdef HAVE_SYS_QUOTA_H
void QuotaInfo::methodLinuxV1(std::string const & dev, uid_t const uid)
{
	// This is struct "dqblk" from "sys/quota.h" for Linux Quota Version 1
	struct
	{
		u_int32_t dqb_bhardlimit;	/* absolute limit on disk blks alloc */
		u_int32_t dqb_bsoftlimit;	/* preferred limit on disk blks */
		u_int32_t dqb_curblocks;	/* current block count */
		u_int32_t dqb_ihardlimit;	/* maximum # allocated inodes */
		u_int32_t dqb_isoftlimit;	/* preferred inode limit */
		u_int32_t dqb_curinodes;	/* current # allocated inodes */
		time_t dqb_btime;		/* time limit for excessive disk use */
		time_t dqb_itime;		/* time limit for excessive files */
	} q;
	// 0x0300 is Q_GETQUOTA from "sys/quota.h" for Linux Quota Version 1
	Sys::quotactl(QCMD(0x0300, sub_), dev.c_str(), uid, (caddr_t) &q);
	blockHL_=q.dqb_bhardlimit;
	blockSL_=q.dqb_bsoftlimit;
	blocks_=q.dqb_curblocks;
	inodeHL_=q.dqb_ihardlimit;
	inodeSL_=q.dqb_isoftlimit;
	inodes_=q.dqb_curinodes;

	blockTL_=q.dqb_btime;
	inodeTL_=q.dqb_itime;
}

void QuotaInfo::methodLinuxV2(std::string const & dev, uid_t const uid)
{
	// This is struct "dqblk" from "sys/quota.h" for Linux Quota Version 2
	struct
	{
		u_int64_t dqb_bhardlimit;   /* absolute limit on disk quota blocks alloc */
		u_int64_t dqb_bsoftlimit;   /* preferred limit on disk quota blocks */
		u_int64_t dqb_curspace;     /* current quota block count */    /* WRONG: This actually holds bytes; absurd@schlund.de 2006-May */
		u_int64_t dqb_ihardlimit;   /* maximum # allocated inodes */
		u_int64_t dqb_isoftlimit;   /* preferred inode limit */
		u_int64_t dqb_curinodes;    /* current # allocated inodes */
		u_int64_t dqb_btime;        /* time limit for excessive disk use */
		u_int64_t dqb_itime;        /* time limit for excessive files */
		u_int32_t dqb_valid;        /* bitmask of QIF_* constants */
	} q;
	// 0x800007 is Q_GETQUOTA from "sys/quota.h" for Linux Quota Version 2
	Sys::quotactl(QCMD(0x800007, sub_), dev.c_str(), uid, (caddr_t) &q);
	blockHL_=q.dqb_bhardlimit;
	blockSL_=q.dqb_bsoftlimit;
	// If block size is not hard coded to BlockSize_, we have a problem here ;(
	blocks_=q.dqb_curspace / BlockSize_;
	if (q.dqb_curspace % BlockSize_ != 0) { ++blocks_; }

	inodeHL_=q.dqb_ihardlimit;
	inodeSL_=q.dqb_isoftlimit;
	inodes_=q.dqb_curinodes;

	blockTL_=q.dqb_btime;
	inodeTL_=q.dqb_itime;
}
#endif

#ifdef HAVE_XFS_XQM_H
void QuotaInfo::methodLinuxXfs(std::string const & dev, uid_t const uid)
{
	struct fs_disk_quota q;
	Sys::quotactl(QCMD(Q_XGETQUOTA,sub_), dev.c_str(), uid, (caddr_t) &q);
	/** @bug: Maybe there is some more effort needed to make this
	 * portable. At least there's no overflow check here (on linux,
	 * values are 64bit; unsgigned long ist 32bit only). */
	blockHL_=q.d_blk_hardlimit >> 1;
	blockSL_=q.d_blk_softlimit >> 1;
	blocks_=q.d_bcount >> 1;

	inodeHL_=q.d_ino_hardlimit;
	inodeSL_=q.d_ino_softlimit;
	inodes_=q.d_icount;

	blockTL_=q.d_btimer;
	inodeTL_=q.d_itimer;
}
#endif

#ifdef __linux__
void QuotaInfo::methodLinuxRpc(std::string const & dev, uid_t const uid)
{
	class RPCClient
	{
	public:
		RPCClient(std::string const & host)
			:clnt_(0)
			,host_(host)
		{
			clnt_ = clnt_create(host.c_str(), RQUOTAPROG, RQUOTAVERS, "udp");
			if (clnt_ == 0) { UI_THROW("RPC clnt_create(3) failed for " + host_); }
			clnt_->cl_auth = authunix_create_default();
		}
		~RPCClient()
		{
			auth_destroy(clnt_->cl_auth);
			clnt_destroy(clnt_);
		}
		getquota_rslt request(std::string const & path, uid_t const uid)
		{
			struct timeval const timeout = {2,0};

			// Set up args
			getquota_args args;
			args.gqa_pathp = (char *) path.c_str();
			args.gqa_uid = uid;

			// Set up result
			getquota_rslt result;
			memset((char *) &result, 0, sizeof(result));

			// Run request
			enum clnt_stat retval(clnt_call(clnt_, RQUOTAPROC_GETQUOTA,
																			(xdrproc_t) xdr_getquota_args, (caddr_t) &args,
																			(xdrproc_t) xdr_getquota_rslt, (caddr_t) &result,
																			timeout));

			// Error handling
			if (retval != RPC_SUCCESS || result.status != Q_OK)
			{
				UI_THROW("RPC clnt_call(3) failed (" + host_ + ":" + path + " uid=" + tos(uid) + "): retval/status=" + tos(retval) + "/" + tos(result.status));
			}
			return result;
		}
	private:
		CLIENT * clnt_;
		std::string const host_;
	};

	// We don't use quotactl for this, and we only have a method for user quota
	if (sub_ == GRPQUOTA)
	{
		UI_THROW("No method for group quota for rpc; device was: " + dev);
	}

	std::string::size_type const col(dev.find_first_of(':'));
	if (col == std::string::npos || col >= dev.length() || dev[col+1] == '(')
	{
		UI_THROW("Invalid NFS mount syntax: " + dev);
	}
	std::string const host(dev.substr(0, col));
	std::string const path(dev.substr(col+1));

	RPCClient client(host);
	getquota_rslt result(client.request(path, uid));
	struct rquota * n(&result.getquota_rslt_u.gqr_rquota);

	// Finally, fill quota vars
	blockHL_ = n->rq_bhardlimit;
	blockSL_ = n->rq_bsoftlimit;
	inodeHL_ = n->rq_fhardlimit;
	inodeSL_ = n->rq_fsoftlimit;
	inodes_ = n->rq_curfiles;
	blocks_ = n->rq_curblocks;

	// Adapt from remote block size if needed
	if ((unsigned long) n->rq_bsize != BlockSize_)
	{
		blockHL_ = (blockHL_ * n->rq_bsize) / BlockSize_;
		blockSL_ = (blockSL_ * n->rq_bsize) / BlockSize_;
		blocks_ = (blocks_ * n->rq_bsize) / BlockSize_;
	}

	//  Timer values are in "time left" -- adapt
	time_t now;
	time(&now);
	blockTL_ = (n->rq_btimeleft ? n->rq_btimeleft + now : 0);
	inodeTL_ = (n->rq_ftimeleft ? n->rq_ftimeleft + now : 0);
}
#endif

std::string   QuotaInfo::getMethod() const  { return method_; }
unsigned long QuotaInfo::getBlockHL() const { return blockHL_; }
unsigned long QuotaInfo::getBlockSL() const { return blockSL_; }
unsigned long QuotaInfo::getBlocks() const  { return blocks_; }
unsigned long QuotaInfo::getINodeHL() const { return inodeHL_; }
unsigned long QuotaInfo::getINodeSL() const { return inodeSL_; }
unsigned long QuotaInfo::getINodes() const  { return inodes_; }
time_t        QuotaInfo::getBlockTL() const { return blockTL_; }
time_t        QuotaInfo::getINodeTL() const { return inodeTL_; }


bool QuotaInfo::getFreeBlocksHL(unsigned long & free) const
{
	return getFree(getBlockHL(), getBlocks(), free);
}

bool QuotaInfo::getFreeBlocksSL(unsigned long & free) const
{
	return getFree(getBlockSL(), getBlocks(), free);
}

bool QuotaInfo::getFreeINodesHL(unsigned long & free) const
{
	return getFree(getINodeHL(), getINodes(), free);
}

bool QuotaInfo::getFreeInodesSL(unsigned long & free) const
{
	return getFree(getINodeSL(), getINodes(), free);
}


bool QuotaInfo::getFree(unsigned long const limit, unsigned long const blocks, unsigned long & free) const
{
	bool haveLimit(limit > 0);
	if (haveLimit)
	{
		free=0;
		if (limit > blocks)
		{
			free = limit-blocks;
		}
	}
	return haveLimit;
}

unsigned long QuotaInfo::BlockSize_(1024);

}}
