--- /dev/null
+/*
+* 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;
+
+ _SEH2_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;
+ _SEH2_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)
+ _SEH2_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;
+ _SEH2_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");
+ _SEH2_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;
+ _SEH2_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;
+ }
+
+ }
+ }
+ _SEH2_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 (!_SEH2_AbnormalTermination()) {
+ if (Status == STATUS_PENDING || Status == STATUS_CANT_WAIT) {
+ Status = Ext2QueueRequest(IrpContext);
+ }
+ else {
+ Ext2CompleteIrpContext(IrpContext, Status);
+ }
+ }
+ } _SEH2_END;
+
+ 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;
+
+ _SEH2_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))
+ _SEH2_LEAVE;
+
+ ExAcquireResourceExclusiveLite(&Vcb->FcbLock, TRUE);
+ FcbLockAcquired = TRUE;
+
+ if (!Mcb)
+ _SEH2_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;
+ _SEH2_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");
+ _SEH2_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;
+ _SEH2_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))
+ _SEH2_LEAVE;
+
+ }
+ } _SEH2_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 (!_SEH2_AbnormalTermination()) {
+ if (Status == STATUS_PENDING || Status == STATUS_CANT_WAIT) {
+ Status = Ext2QueueRequest(IrpContext);
+ }
+ else {
+ Ext2CompleteIrpContext(IrpContext, Status);
+ }
+ }
+ } _SEH2_END;
+ return Status;
+}