/*
* COPYRIGHT:        See COPYRIGHT.TXT
* PROJECT:          Ext2 File System Driver for Windows >= NT
* FILE:             ea.c
* PROGRAMMER:       Matt Wu <mattwu@163.com>  Kaho Ng <ngkaho1234@gmail.com>
* HOMEPAGE:         http://www.ext2fsd.com
* UPDATE HISTORY:
*/

/* INCLUDES *****************************************************************/

#include "ext2fs.h"
#include <linux/ext4_xattr.h>

#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE, Ext2QueryEa)
#pragma alloc_text(PAGE, Ext2SetEa)
#pragma alloc_text(PAGE, Ext2IsEaNameValid)
#endif

// Ea iterator
struct EaIterator {
	// Return only an entry
	BOOLEAN ReturnSingleEntry;

	// Is the buffer overflowing?
	BOOL OverFlow;

	// FILE_FULL_EA_INFORMATION output buffer
	PFILE_FULL_EA_INFORMATION FullEa;
	PFILE_FULL_EA_INFORMATION LastFullEa;

	// UserBuffer's size
	ULONG UserBufferLength;

	// Remaining UserBuffer's size
	ULONG RemainingUserBufferLength;

	// Start scanning from this EA
	ULONG EaIndex;

	// Next EA index returned by Ext2IterateAllEa
	ULONG EaIndexCounter;
};

static int Ext2IterateAllEa(struct ext4_xattr_ref *xattr_ref, struct ext4_xattr_item *item, BOOL is_last)
{
	struct EaIterator *pEaIterator = xattr_ref->iter_arg;
	ULONG EaEntrySize = 4 + 1 + 1 + 2 + item->name_len + 1 + item->data_size;
	ASSERT(pEaIterator);
	if (!is_last && !pEaIterator->ReturnSingleEntry)
		EaEntrySize = ALIGN_UP(EaEntrySize, ULONG);

	// Start iteration from index specified
	if (pEaIterator->EaIndexCounter < pEaIterator->EaIndex) {
		pEaIterator->EaIndexCounter++;
		return EXT4_XATTR_ITERATE_CONT;
	}
	pEaIterator->EaIndexCounter++;

	if (EaEntrySize > pEaIterator->RemainingUserBufferLength) {
		pEaIterator->OverFlow = TRUE;
		return EXT4_XATTR_ITERATE_STOP;
	}
	pEaIterator->FullEa->NextEntryOffset = 0;
	pEaIterator->FullEa->Flags = 0;
	pEaIterator->FullEa->EaNameLength = (UCHAR)item->name_len;
	pEaIterator->FullEa->EaValueLength = (USHORT)item->data_size;
	RtlCopyMemory(&pEaIterator->FullEa->EaName[0],
		item->name,
		item->name_len);
	RtlCopyMemory(&pEaIterator->FullEa->EaName[0] + item->name_len + 1,
		item->data,
		item->data_size);

	// Link FullEa and LastFullEa together
	if (pEaIterator->LastFullEa) {
		pEaIterator->LastFullEa->NextEntryOffset = (ULONG)
			((PCHAR)pEaIterator->FullEa - (PCHAR)pEaIterator->LastFullEa);
	}

	pEaIterator->LastFullEa = pEaIterator->FullEa;
	pEaIterator->FullEa = (PFILE_FULL_EA_INFORMATION)
			((PCHAR)pEaIterator->FullEa + EaEntrySize);
	pEaIterator->RemainingUserBufferLength -= EaEntrySize;

	if (pEaIterator->ReturnSingleEntry)
		return EXT4_XATTR_ITERATE_STOP;

	return EXT4_XATTR_ITERATE_CONT;
}

NTSTATUS
Ext2QueryEa (
	IN PEXT2_IRP_CONTEXT    IrpContext
)
{
	PIRP                Irp = NULL;
	PIO_STACK_LOCATION  IrpSp;

	PDEVICE_OBJECT      DeviceObject;

	PEXT2_VCB           Vcb = NULL;
	PEXT2_FCB           Fcb = NULL;
	PEXT2_CCB           Ccb = NULL;
	PEXT2_MCB           Mcb = NULL;

	PUCHAR  UserEaList;
	ULONG   UserEaListLength;
	ULONG   UserEaIndex;

	BOOLEAN RestartScan;
	BOOLEAN ReturnSingleEntry;
	BOOLEAN IndexSpecified;

	BOOLEAN             MainResourceAcquired = FALSE;
	BOOLEAN             XattrRefAcquired = FALSE;

	NTSTATUS            Status = STATUS_UNSUCCESSFUL;

	struct ext4_xattr_ref xattr_ref;
	PCHAR UserBuffer;

	ULONG UserBufferLength = 0;
	ULONG RemainingUserBufferLength = 0;

	PFILE_FULL_EA_INFORMATION FullEa, LastFullEa = NULL;

	__try {

		Ccb = IrpContext->Ccb;
		ASSERT(Ccb != NULL);
		ASSERT((Ccb->Identifier.Type == EXT2CCB) &&
			(Ccb->Identifier.Size == sizeof(EXT2_CCB)));
		DeviceObject = IrpContext->DeviceObject;
		Vcb = (PEXT2_VCB)DeviceObject->DeviceExtension;
		Fcb = IrpContext->Fcb;
		Mcb = Fcb->Mcb;
		Irp = IrpContext->Irp;
		IrpSp = IoGetCurrentIrpStackLocation(Irp);

		Irp->IoStatus.Information = 0;

		//
		// Receive input parameter from caller
		//
		UserBuffer = Ext2GetUserBuffer(Irp);
		if (!UserBuffer) {
			Status = STATUS_INSUFFICIENT_RESOURCES;
			__leave;
		}
		UserBufferLength = IrpSp->Parameters.QueryEa.Length;
		RemainingUserBufferLength = UserBufferLength;
		UserEaList = IrpSp->Parameters.QueryEa.EaList;
		UserEaListLength = IrpSp->Parameters.QueryEa.EaListLength;
		UserEaIndex = IrpSp->Parameters.QueryEa.EaIndex;
		RestartScan = BooleanFlagOn(IrpSp->Flags, SL_RESTART_SCAN);
		ReturnSingleEntry = BooleanFlagOn(IrpSp->Flags, SL_RETURN_SINGLE_ENTRY);
		IndexSpecified = BooleanFlagOn(IrpSp->Flags, SL_INDEX_SPECIFIED);

		if (!Mcb)
			__leave;

		//
		// We do not allow multiple instance gaining EA access to the same file
		//
		if (!ExAcquireResourceExclusiveLite(
			&Fcb->MainResource,
			IsFlagOn(IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT))) {
			Status = STATUS_PENDING;
			__leave;
		}
		MainResourceAcquired = TRUE;

		Status = Ext2WinntError(ext4_fs_get_xattr_ref(IrpContext, Vcb, Fcb->Mcb, &xattr_ref));
		if (!NT_SUCCESS(Status)) {
			DbgPrint("ext4_fs_get_xattr_ref() failed!\n");
			__leave;
		}

		FullEa = (PFILE_FULL_EA_INFORMATION)UserBuffer;

		XattrRefAcquired = TRUE;

		if (RemainingUserBufferLength)
			RtlZeroMemory(FullEa, RemainingUserBufferLength);

		if (UserEaList != NULL) {
			int i = 0;
			PFILE_GET_EA_INFORMATION GetEa;
			for (GetEa = (PFILE_GET_EA_INFORMATION)&UserEaList[0];
					GetEa < (PFILE_GET_EA_INFORMATION)((PUCHAR)UserEaList
					+ UserEaListLength);
				GetEa = (GetEa->NextEntryOffset == 0
					? (PFILE_GET_EA_INFORMATION)MAXUINT_PTR
					: (PFILE_GET_EA_INFORMATION)((PUCHAR)GetEa
						+ GetEa->NextEntryOffset))) {

				size_t ItemSize;
				OEM_STRING Str;
				ULONG EaEntrySize;
				BOOL is_last = !GetEa->NextEntryOffset;

				Str.MaximumLength = Str.Length = GetEa->EaNameLength;
				Str.Buffer = &GetEa->EaName[0];

				//
				// At the moment we only need to know whether the item exists
				// and its size.
				//
				Status = Ext2WinntError(ext4_fs_get_xattr(&xattr_ref,
					EXT4_XATTR_INDEX_USER,
					Str.Buffer,
					Str.Length,
					NULL,
					0,
					&ItemSize));
				if (!NT_SUCCESS(Status))
					continue;

				//
				//  We were not able to locate the name therefore we must
				//  dummy up a entry for the query.  The needed Ea size is
				//  the size of the name + 4 (next entry offset) + 1 (flags)
				//  + 1 (name length) + 2 (value length) + the name length +
				//  1 (null byte) + Data Size.
				//
				EaEntrySize = 4 + 1 + 1 + 2 + GetEa->EaNameLength + 1 + ItemSize;
				if (!is_last)
					EaEntrySize = ALIGN_UP(EaEntrySize, ULONG);

				if (EaEntrySize > RemainingUserBufferLength) {

					Status = i ? STATUS_BUFFER_OVERFLOW : STATUS_BUFFER_TOO_SMALL;
					__leave;
				}
				FullEa->NextEntryOffset = 0;
				FullEa->Flags = 0;
				FullEa->EaNameLength = GetEa->EaNameLength;
				FullEa->EaValueLength = (USHORT)ItemSize;
				RtlCopyMemory(&FullEa->EaName[0],
					&GetEa->EaName[0],
					GetEa->EaNameLength);

				//
				// This query must succeed, or is guarenteed to succeed
				// since we are only looking up
				// an EA entry in a in-memory tree structure.
				// Otherwise that means someone might be operating on
				// the xattr_ref without acquiring Inode lock.
				//
				ASSERT(NT_SUCCESS(Ext2WinntError(
					ext4_fs_get_xattr(&xattr_ref,
						EXT4_XATTR_INDEX_USER,
						Str.Buffer,
						Str.Length,
						&FullEa->EaName[0] + FullEa->EaNameLength + 1,
						ItemSize,
						&ItemSize
				))));
				FullEa->EaValueLength = (USHORT)ItemSize;

				// Link FullEa and LastFullEa together
				if (LastFullEa)
					LastFullEa->NextEntryOffset = (ULONG)((PCHAR)FullEa - 
                                                          (PCHAR)LastFullEa);

				LastFullEa = FullEa;
				FullEa = (PFILE_FULL_EA_INFORMATION)
					((PCHAR)FullEa + EaEntrySize);
				RemainingUserBufferLength -= EaEntrySize;
				i++;
			}
		} else if (IndexSpecified) {
			struct EaIterator EaIterator;
			//
			//  The user supplied an index into the Ea list.
			//
			if (RemainingUserBufferLength)
				RtlZeroMemory(FullEa, RemainingUserBufferLength);

			EaIterator.OverFlow = FALSE;
			EaIterator.RemainingUserBufferLength = UserBufferLength;
			// In this case, return only an entry.
			EaIterator.ReturnSingleEntry = TRUE;
			EaIterator.FullEa = (PFILE_FULL_EA_INFORMATION)UserBuffer;
			EaIterator.LastFullEa = NULL;
			EaIterator.UserBufferLength = UserBufferLength;
			EaIterator.EaIndex = UserEaIndex;
			EaIterator.EaIndexCounter = 1;

			xattr_ref.iter_arg = &EaIterator;
			ext4_fs_xattr_iterate(&xattr_ref, Ext2IterateAllEa);

			RemainingUserBufferLength = EaIterator.RemainingUserBufferLength;

			Status = STATUS_SUCCESS;

			// It seems that the item isn't found
			if (RemainingUserBufferLength == UserBufferLength)
				Status = STATUS_OBJECTID_NOT_FOUND;

			if (EaIterator.OverFlow) {
				if (RemainingUserBufferLength == UserBufferLength)
					Status = STATUS_BUFFER_TOO_SMALL;
				else
					Status = STATUS_BUFFER_OVERFLOW;
			}

		} else {
			struct EaIterator EaIterator;
			//
			//  Else perform a simple scan, taking into account the restart
			//  flag and the position of the next Ea stored in the Ccb.
			//
			if (RestartScan)
				Ccb->EaIndex = 1;

			if (RemainingUserBufferLength)
				RtlZeroMemory(FullEa, RemainingUserBufferLength);

			EaIterator.OverFlow = FALSE;
			EaIterator.RemainingUserBufferLength = UserBufferLength;
			EaIterator.ReturnSingleEntry = ReturnSingleEntry;
			EaIterator.FullEa = (PFILE_FULL_EA_INFORMATION)UserBuffer;
			EaIterator.LastFullEa = NULL;
			EaIterator.UserBufferLength = UserBufferLength;
			EaIterator.EaIndex = Ccb->EaIndex;
			EaIterator.EaIndexCounter = 1;

			xattr_ref.iter_arg = &EaIterator;
			ext4_fs_xattr_iterate(&xattr_ref, Ext2IterateAllEa);

			RemainingUserBufferLength = EaIterator.RemainingUserBufferLength;

			if (Ccb->EaIndex < EaIterator.EaIndexCounter)
				Ccb->EaIndex = EaIterator.EaIndexCounter;

			Status = STATUS_SUCCESS;

			if (EaIterator.OverFlow) {
				if (RemainingUserBufferLength == UserBufferLength)
					Status = STATUS_BUFFER_TOO_SMALL;
				else
					Status = STATUS_BUFFER_OVERFLOW;
			}

		}
	}
	__finally {

		if (XattrRefAcquired) {
			if (!NT_SUCCESS(Status)) {
				xattr_ref.dirty = FALSE;
				ext4_fs_put_xattr_ref(&xattr_ref);
			}
			else
				Status = Ext2WinntError(ext4_fs_put_xattr_ref(&xattr_ref));
		}

		if (MainResourceAcquired) {
			ExReleaseResourceLite(&Fcb->MainResource);
		}

		if (NT_SUCCESS(Status)) {
			Ext2NotifyReportChange(
				IrpContext,
				Vcb,
				Mcb,
				FILE_NOTIFY_CHANGE_EA,
				FILE_ACTION_MODIFIED);
			Irp->IoStatus.Information = UserBufferLength - RemainingUserBufferLength;
		}

		if (!AbnormalTermination()) {
			if (Status == STATUS_PENDING || Status == STATUS_CANT_WAIT) {
				Status = Ext2QueueRequest(IrpContext);
			}
			else {
				Ext2CompleteIrpContext(IrpContext, Status);
			}
		}
	}

	return Status;
}

BOOLEAN
Ext2IsEaNameValid(
	IN OEM_STRING Name
)
{
	ULONG Index;
	UCHAR Char;

	//
	//  Empty names are not valid
	//

	if (Name.Length == 0)
		return FALSE;

	//
	// Do not allow EA name longer than 255 bytes
	//
	if (Name.Length > 255)
		return FALSE;

	for (Index = 0; Index < (ULONG)Name.Length; Index += 1) {

		Char = Name.Buffer[Index];

		//
		//  Skip over and Dbcs chacters
		//
		if (FsRtlIsLeadDbcsCharacter(Char)) {

			ASSERT(Index != (ULONG)(Name.Length - 1));
			Index += 1;
			continue;
		}

		//
		//  Make sure this character is legal, and if a wild card, that
		//  wild cards are permissible.
		//
		if (!FsRtlIsAnsiCharacterLegalFat(Char, FALSE))
			return FALSE;

	}

	return TRUE;
}

NTSTATUS
Ext2SetEa (
	IN PEXT2_IRP_CONTEXT    IrpContext
)
{
	PIRP                Irp = NULL;
	PIO_STACK_LOCATION  IrpSp;

	PDEVICE_OBJECT      DeviceObject;

	PEXT2_VCB           Vcb = NULL;
	PEXT2_FCB           Fcb = NULL;
	PEXT2_CCB           Ccb = NULL;
	PEXT2_MCB           Mcb = NULL;

	BOOLEAN             MainResourceAcquired = FALSE;
	BOOLEAN             FcbLockAcquired = FALSE;
	BOOLEAN             XattrRefAcquired = FALSE;

	NTSTATUS            Status = STATUS_UNSUCCESSFUL;

	struct ext4_xattr_ref xattr_ref;
	PCHAR UserBuffer;
	ULONG UserBufferLength;

	PFILE_FULL_EA_INFORMATION FullEa;

	__try {

		Ccb = IrpContext->Ccb;
		ASSERT(Ccb != NULL);
		ASSERT((Ccb->Identifier.Type == EXT2CCB) &&
			(Ccb->Identifier.Size == sizeof(EXT2_CCB)));
		DeviceObject = IrpContext->DeviceObject;
		Vcb = (PEXT2_VCB)DeviceObject->DeviceExtension;
		Fcb = IrpContext->Fcb;
		Mcb = Fcb->Mcb;
		Irp = IrpContext->Irp;
		IrpSp = IoGetCurrentIrpStackLocation(Irp);

		Irp->IoStatus.Information = 0;

		//
		// Receive input parameter from caller
		//
		UserBufferLength = IrpSp->Parameters.SetEa.Length;
		UserBuffer = Irp->UserBuffer;

		// Check if the EA buffer provided is valid
		Status = IoCheckEaBufferValidity((PFILE_FULL_EA_INFORMATION)UserBuffer,
			UserBufferLength,
			(PULONG)&Irp->IoStatus.Information);
		if (!NT_SUCCESS(Status))
			__leave;

		ExAcquireResourceExclusiveLite(&Vcb->FcbLock, TRUE);
		FcbLockAcquired = TRUE;

		if (!Mcb)
			__leave;

		//
		// We do not allow multiple instance gaining EA access to the same file
		//
		if (!ExAcquireResourceExclusiveLite(
			&Fcb->MainResource,
			IsFlagOn(IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT))) {
			Status = STATUS_PENDING;
			__leave;
		}
		MainResourceAcquired = TRUE;

		Status = Ext2WinntError(ext4_fs_get_xattr_ref(IrpContext, Vcb, Fcb->Mcb, &xattr_ref));
		if (!NT_SUCCESS(Status)) {
			DbgPrint("ext4_fs_get_xattr_ref() failed!\n");
			__leave;
		}

		XattrRefAcquired = TRUE;

		//
		// Remove all existing EA entries.
		//
		ext4_xattr_purge_items(&xattr_ref);
		xattr_ref.dirty = TRUE;
		Status = STATUS_SUCCESS;

		// Iterate the whole EA buffer to do inspection
		for (FullEa = (PFILE_FULL_EA_INFORMATION)UserBuffer;
			FullEa < (PFILE_FULL_EA_INFORMATION)&UserBuffer[UserBufferLength];
			FullEa = (PFILE_FULL_EA_INFORMATION)(FullEa->NextEntryOffset == 0 ?
				&UserBuffer[UserBufferLength] :
				(PCHAR)FullEa + FullEa->NextEntryOffset)) {

			OEM_STRING EaName;

			EaName.MaximumLength = EaName.Length = FullEa->EaNameLength;
			EaName.Buffer = &FullEa->EaName[0];

			// Check if EA's name is valid
			if (!Ext2IsEaNameValid(EaName)) {
				Irp->IoStatus.Information = (PCHAR)FullEa - UserBuffer;
				Status = STATUS_INVALID_EA_NAME;
				__leave;
			}
		}

		// Now add EA entries to the inode
		for (FullEa = (PFILE_FULL_EA_INFORMATION)UserBuffer;
			FullEa < (PFILE_FULL_EA_INFORMATION)&UserBuffer[UserBufferLength];
			FullEa = (PFILE_FULL_EA_INFORMATION)(FullEa->NextEntryOffset == 0 ?
				&UserBuffer[UserBufferLength] :
				(PCHAR)FullEa + FullEa->NextEntryOffset)) {

				int ret;
				OEM_STRING EaName;

				EaName.MaximumLength = EaName.Length = FullEa->EaNameLength;
				EaName.Buffer = &FullEa->EaName[0];

				Status = Ext2WinntError(ret =
					ext4_fs_set_xattr_ordered(&xattr_ref,
						EXT4_XATTR_INDEX_USER,
						EaName.Buffer,
						EaName.Length,
						&FullEa->EaName[0] + FullEa->EaNameLength + 1,
						FullEa->EaValueLength));
				if (!NT_SUCCESS(Status))
					__leave;

		}
	} __finally {

		if (XattrRefAcquired) {
			if (!NT_SUCCESS(Status)) {
				xattr_ref.dirty = FALSE;
				ext4_fs_put_xattr_ref(&xattr_ref);
			} else
				Status = Ext2WinntError(ext4_fs_put_xattr_ref(&xattr_ref));
		}

		if (FcbLockAcquired) {
			ExReleaseResourceLite(&Vcb->FcbLock);
			FcbLockAcquired = FALSE;
		}

		if (MainResourceAcquired) {
			ExReleaseResourceLite(&Fcb->MainResource);
		}

		if (NT_SUCCESS(Status)) {
			Ext2NotifyReportChange(
				IrpContext,
				Vcb,
				Mcb,
				FILE_NOTIFY_CHANGE_EA,
				FILE_ACTION_MODIFIED);
		}

		if (!AbnormalTermination()) {
			if (Status == STATUS_PENDING || Status == STATUS_CANT_WAIT) {
				Status = Ext2QueueRequest(IrpContext);
			}
			else {
				Ext2CompleteIrpContext(IrpContext, Status);
			}
		}
	}
	return Status;
}
