/*
   Copyright 2013-2014 EditShare, 2013-2015 Skytechnology sp. z o.o.

   This file is part of LizardFS.

   LizardFS 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, version 3.

   LizardFS 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 General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with LizardFS. If not, see <http://www.gnu.org/licenses/>.
 */

#include "common/platform.h"
#include "common/acl_converter.h"

static constexpr uint16_t kUserPermissionsModeOffset  = 0100;
static constexpr uint16_t kGroupPermissionsModeOffset = 010;
static constexpr uint16_t kOtherPermissionsModeOffset = 01;

static bool checkPermissions(uint16_t perm) {
	return (perm & ~(ACL_READ | ACL_WRITE | ACL_EXECUTE)) == 0;
}

static uint16_t assertPermissionsOnTheFly(uint16_t perm) {
	if (!checkPermissions(perm)) {
		throw aclConverter::AclConversionException("Invalid permissions mask");
	}
	return perm;
}

static uint16_t extractMask(uint16_t mode, uint16_t offset) {
	uint16_t mask = uint16_t((mode / offset) & 07);
	return assertPermissionsOnTheFly(mask);
}

static void insertToMode(uint16_t& mode, uint16_t offset, uint8_t newMask) {
	uint8_t oldMask = (mode / offset) & 07;
	mode -= oldMask * offset;
	mode += newMask * offset;
}

static void prepareExtendedAcl(AccessControlList& acl) {
	if (!acl.extendedAcl) {
		acl.extendedAcl.reset(new ExtendedAcl);
		acl.extendedAcl->setOwningGroupMask(extractMask(acl.mode, kGroupPermissionsModeOffset));
	}
}

static void validateNonIdEntry(uint8_t tags, const PosixAclXattrEntry& entry) {
	// These entries can appear only once...
	if (tags & entry.tag) {
		throw aclConverter::AclConversionException("Entry duplication");
	}
	// ...and with undefined id
	if (entry.id != ACL_UNDEFINED_ID) {
		throw aclConverter::AclConversionException("Entry with defined ID");
	}
}

static void validateIdEntry(const AccessControlList& acl, const PosixAclXattrEntry& entry) {
	if (acl.extendedAcl &&
			acl.extendedAcl->hasEntryFor(ExtendedAcl::EntryType::kNamedGroup, entry.id)) {
		throw IncorrectDeserializationException("Group entry duplication");
	}
}

PosixAclXattr aclConverter::extractPosixObject(const uint8_t* buffer, uint32_t bufferSize) {
	PosixAclXattr posix;
	try {
		posix.read(buffer, bufferSize);
	} catch (Exception&) {
		throw AclConversionException("Data doesn't contain ACL");
	}
	return posix;
}


AccessControlList aclConverter::posixToAclObject(const PosixAclXattr& posix) {
	if (posix.version != POSIX_ACL_XATTR_VERSION) {
		throw AclConversionException("Incorrect POSIX ACL xattr version: " +
				std::to_string(posix.version));
	}
	if (posix.entries.empty()) {
		throw AclConversionException("Empty POSIX ACL xattr object");
	}

	AccessControlList acl(0);
	// NOTE Documentation says nothing about the order of entries (it's supposed
	//      to be ascending by a tag value), so we allow any order.
	uint8_t appearedTagsBitmask = 0x00;
	for (const PosixAclXattrEntry& entry : posix.entries) {
		assertPermissionsOnTheFly(entry.perm); // Check permissions mask
		switch (entry.tag) {
			case ACL_USER_OBJ:
				validateNonIdEntry(appearedTagsBitmask, entry);
				insertToMode(acl.mode, kUserPermissionsModeOffset, entry.perm);
				break;
			case ACL_USER:
				validateIdEntry(acl, entry);
				prepareExtendedAcl(acl);
				acl.extendedAcl->addNamedUser(entry.id, entry.perm);
				break;
			case ACL_GROUP_OBJ:
				validateNonIdEntry(appearedTagsBitmask, entry);
				// If eacl exists, store owning group permissions there
				if (acl.extendedAcl) {
					acl.extendedAcl->setOwningGroupMask(entry.perm);
				} else {
					insertToMode(acl.mode, kGroupPermissionsModeOffset, entry.perm);
				}
				break;
			case ACL_GROUP:
				validateIdEntry(acl, entry);
				prepareExtendedAcl(acl);
				acl.extendedAcl->addNamedGroup(entry.id, entry.perm);
				break;
			case ACL_MASK:
				validateNonIdEntry(appearedTagsBitmask, entry);
				prepareExtendedAcl(acl); // Move owning group permissions to eacl if necessary
				insertToMode(acl.mode, kGroupPermissionsModeOffset, entry.perm);
				break;
			case ACL_OTHER:
				validateNonIdEntry(appearedTagsBitmask, entry);
				insertToMode(acl.mode, kOtherPermissionsModeOffset, entry.perm);
				break;
			default:
				throw AclConversionException("Unknown ACL xattr entry tag");
		}
		appearedTagsBitmask |= entry.tag;
	}

	// Check if at least minimal ACL appeared
	if ((appearedTagsBitmask & (ACL_USER_OBJ | ACL_GROUP_OBJ | ACL_OTHER)) !=
			(ACL_USER_OBJ | ACL_GROUP_OBJ | ACL_OTHER)) {
		throw AclConversionException("ACL xattr without all minimal ACL entries");
	}
	// Extended ACL without mask is invalid
	if ((appearedTagsBitmask & (ACL_GROUP | ACL_USER)) && !(appearedTagsBitmask & ACL_MASK)) {
		throw AclConversionException("Extended ACL without permissions mask");
	}

	return acl;
}

std::vector<uint8_t> aclConverter::aclObjectToXattr(const AccessControlList& acl) {
	// NOTE Documentation says nothing about the order of entries. It's supposed
	//      to be ascending by a tag value, so we implement it.
	PosixAclXattr xattr;
	xattr.version = POSIX_ACL_XATTR_VERSION;

	// Owner user permissions
	xattr.entries.push_back(
			{ACL_USER_OBJ, extractMask(acl.mode, kUserPermissionsModeOffset), ACL_UNDEFINED_ID});

	if (acl.extendedAcl) {
		// Users permissions
		for (ExtendedAcl::Entry entry : acl.extendedAcl->list()) {
			if (entry.type != ExtendedAcl::EntryType::kNamedUser) {
				continue;
			}
			xattr.entries.push_back(
					{ACL_USER, assertPermissionsOnTheFly(entry.mask), entry.id});
		}

		// Owning group permissions from ExtendedAcl
		xattr.entries.push_back({ACL_GROUP_OBJ,
				assertPermissionsOnTheFly(acl.extendedAcl->owningGroupMask()), ACL_UNDEFINED_ID});

		// Groups permissions
		for (ExtendedAcl::Entry entry : acl.extendedAcl->list()) {
			if (entry.type != ExtendedAcl::EntryType::kNamedGroup) {
				continue;
			}
			xattr.entries.push_back(
					{ACL_GROUP, assertPermissionsOnTheFly(entry.mask), entry.id});
		}

		// Permissions mask
		xattr.entries.push_back(
				{ACL_MASK, extractMask(acl.mode, kGroupPermissionsModeOffset), ACL_UNDEFINED_ID});
	} else {
		// Owning group permissions from mode
		xattr.entries.push_back({ACL_GROUP_OBJ,
				extractMask(acl.mode, kGroupPermissionsModeOffset), ACL_UNDEFINED_ID});
	}

	// Other permissions
	xattr.entries.push_back(
			{ACL_OTHER, extractMask(acl.mode, kOtherPermissionsModeOffset), ACL_UNDEFINED_ID});

	// Write to buffer
	std::vector<uint8_t> buffer(xattr.rawSize());
	size_t writtenSize = xattr.write(buffer.data());
	if (writtenSize != buffer.size()) {
		throw AclConversionException("xattr data incorrectly written to a buffer");
	}
	return buffer;
}
