Index: fcb.c =================================================================== --- drivers/filesystems/fastfat/fcb.c (revision 69845) +++ drivers/filesystems/fastfat/fcb.c (working copy) @@ -437,6 +437,7 @@ DPRINT("'%wZ'\n", PathNameU); + ASSERT(PathNameU->Length >= sizeof(WCHAR) && PathNameU->Buffer[0] == L'\\'); Hash = vfatNameHash(0, PathNameU); entry = pVCB->FcbHashTable[Hash % pVCB->HashTableSize]; @@ -790,182 +791,342 @@ NTSTATUS status; PVFATFCB FCB = NULL; PVFATFCB parentFCB; - UNICODE_STRING NameU; UNICODE_STRING RootNameU = RTL_CONSTANT_STRING(L"\\"); UNICODE_STRING FileNameU; WCHAR NameBuffer[260]; - PWCHAR curr, prev, last; - ULONG Length; + UNICODE_STRING Component; + UNICODE_STRING PartialName; + PWCHAR Previous, Current, Last; + BOOLEAN TrailingBackslash; DPRINT("vfatGetFCBForFile (%p,%p,%p,%wZ)\n", pVCB, pParentFCB, pFCB, pFileNameU); - FileNameU.Buffer = NameBuffer; - FileNameU.MaximumLength = sizeof(NameBuffer); - RtlCopyUnicodeString(&FileNameU, pFileNameU); + RtlInitEmptyUnicodeString(&FileNameU, NameBuffer, sizeof(NameBuffer)); parentFCB = *pParentFCB; - if (parentFCB == NULL) { - // Trivial case, open of the root directory on volume - if (RtlEqualUnicodeString(&FileNameU, &RootNameU, FALSE)) + /* Absolute path must start with a \ */ + ASSERT(pFileNameU->Length >= sizeof(WCHAR) && pFileNameU->Buffer[0] == L'\\'); + + /* Already the full name, just copy it */ + if (pFileNameU->Length > FileNameU.MaximumLength) { - DPRINT("returning root FCB\n"); - - FCB = vfatOpenRootFCB(pVCB); - *pFCB = FCB; - *pParentFCB = NULL; - - return (FCB != NULL) ? STATUS_SUCCESS : STATUS_OBJECT_PATH_NOT_FOUND; + DPRINT1("Name too long: '%wZ'\n", pFileNameU); + parentFCB = NULL; + status = STATUS_OBJECT_NAME_INVALID; + goto Exit; } + RtlCopyUnicodeString(&FileNameU, pFileNameU); - /* Check for an existing FCB */ - FCB = vfatGrabFCBFromTable(pVCB, &FileNameU); - if (FCB) + /* We start off with the root directory */ + FCB = vfatOpenRootFCB(pVCB); + Previous = Current = FileNameU.Buffer + 1; + PartialName = FileNameU; + PartialName.Length = sizeof(WCHAR); + RtlInitEmptyUnicodeString(&Component, + Previous, + FileNameU.MaximumLength - sizeof(WCHAR)); + } + else + { + /* Starting with a \ is not allowed */ + if (pFileNameU->Length >= sizeof(WCHAR) && pFileNameU->Buffer[0] == L'\\') { - *pFCB = FCB; - *pParentFCB = FCB->parentFcb; - vfatGrabFCB(pVCB, *pParentFCB); - return STATUS_SUCCESS; + DPRINT1("Relative path starts with a backslash: '%wZ' '%wZ'\n", &parentFCB->PathNameU, pFileNameU); + vfatReleaseFCB(pVCB, parentFCB); + parentFCB = NULL; + status = STATUS_OBJECT_NAME_INVALID; + goto Exit; } - last = curr = FileNameU.Buffer + FileNameU.Length / sizeof(WCHAR) - 1; - while (*curr != L'\\' && curr > FileNameU.Buffer) + /* Set up PartialName to the parent FCB */ + PartialName = FileNameU; + PartialName.Length = parentFCB->PathNameU.Length; + + if (parentFCB->PathNameU.Length == sizeof(WCHAR)) { - curr--; - } + /* Parent is the Root, already has a backslash */ + ASSERT(parentFCB->PathNameU.Buffer[0] == L'\\'); - if (curr > FileNameU.Buffer) - { - NameU.Buffer = FileNameU.Buffer; - NameU.MaximumLength = NameU.Length = (curr - FileNameU.Buffer) * sizeof(WCHAR); - FCB = vfatGrabFCBFromTable(pVCB, &NameU); - if (FCB) + /* Parent directory */ + if (parentFCB->PathNameU.Length + pFileNameU->Length > FileNameU.MaximumLength) { - Length = (curr - FileNameU.Buffer) * sizeof(WCHAR); - if (Length != FCB->PathNameU.Length) - { - if (FileNameU.Length + FCB->PathNameU.Length - Length > FileNameU.MaximumLength) - { - vfatReleaseFCB(pVCB, FCB); - return STATUS_OBJECT_NAME_INVALID; - } - RtlMoveMemory(FileNameU.Buffer + FCB->PathNameU.Length / sizeof(WCHAR), - curr, FileNameU.Length - Length); - FileNameU.Length += (USHORT)(FCB->PathNameU.Length - Length); - curr = FileNameU.Buffer + FCB->PathNameU.Length / sizeof(WCHAR); - last = FileNameU.Buffer + FileNameU.Length / sizeof(WCHAR) - 1; - } - RtlCopyMemory(FileNameU.Buffer, FCB->PathNameU.Buffer, FCB->PathNameU.Length); + DPRINT1("Name too long: '%wZ' '%wZ'\n", &parentFCB->PathNameU, pFileNameU); + vfatReleaseFCB(pVCB, parentFCB); + parentFCB = NULL; + status = STATUS_OBJECT_NAME_INVALID; + goto Exit; } + RtlCopyUnicodeString(&FileNameU, &parentFCB->PathNameU); + + /* Relative path */ + status = RtlAppendUnicodeStringToString(&FileNameU, pFileNameU); + ASSERT(status == STATUS_SUCCESS); + + /* Point behind the backslash */ + Current = FileNameU.Buffer + PartialName.Length / sizeof(WCHAR); } else { - FCB = NULL; - } + /* Parent is not the root, should not end with backslash */ + ASSERT(parentFCB->PathNameU.Length >= sizeof(WCHAR)); + ASSERT(parentFCB->PathNameU.Buffer[parentFCB->PathNameU.Length / sizeof(WCHAR) - 1] != L'\\'); - if (FCB == NULL) - { - FCB = vfatOpenRootFCB(pVCB); - curr = FileNameU.Buffer; + /* Parent directory */ + if (parentFCB->PathNameU.Length + RootNameU.Length + pFileNameU->Length > FileNameU.MaximumLength) + { + DPRINT1("Name too long: '%wZ' \\ '%wZ'\n", &parentFCB->PathNameU, pFileNameU); + vfatReleaseFCB(pVCB, parentFCB); + parentFCB = NULL; + status = STATUS_OBJECT_NAME_INVALID; + goto Exit; + } + RtlCopyUnicodeString(&FileNameU, &parentFCB->PathNameU); + + /* Backslash */ + status = RtlAppendUnicodeStringToString(&FileNameU, &RootNameU); + ASSERT(status == STATUS_SUCCESS); + + /* Relative path */ + status = RtlAppendUnicodeStringToString(&FileNameU, pFileNameU); + ASSERT(status == STATUS_SUCCESS); + + /* Point behind the backslash */ + Current = FileNameU.Buffer + PartialName.Length / sizeof(WCHAR) + 1; } + /* Set up an empty Component */ + Previous = Current; + RtlInitEmptyUnicodeString(&Component, + Previous, + FileNameU.MaximumLength - PartialName.Length - sizeof(WCHAR)); + + /* Start setting the parentFCB as the current */ + FCB = parentFCB; parentFCB = NULL; - prev = curr; } - else + + /* Now we must have an absolute path */ + ASSERT(FileNameU.Length >= sizeof(WCHAR) && FileNameU.Buffer[0] == L'\\'); + ASSERT(parentFCB == NULL); + + if (FileNameU.Buffer[FileNameU.Length / sizeof(WCHAR) - 1] == L'\\') { - FCB = parentFCB; - parentFCB = NULL; - prev = curr = FileNameU.Buffer - 1; - last = FileNameU.Buffer + FileNameU.Length / sizeof(WCHAR) - 1; + TrailingBackslash = TRUE; + FileNameU.Length -= sizeof(WCHAR); } - while (curr <= last) + /* Keep a pointer to the last character */ + Last = FileNameU.Buffer + FileNameU.Length / sizeof(WCHAR) - 1; + + status = STATUS_SUCCESS; + while (Current <= Last) { + /* FCB will become our parentFCB, we don't need its parent anymore */ if (parentFCB) { vfatReleaseFCB(pVCB, parentFCB); parentFCB = NULL; } - // fail if element in FCB is not a directory + + /* To be a parent, FCB needs to represent a directory */ if (!vfatFCBIsDirectory(FCB)) { - DPRINT ("Element in requested path is not a directory\n"); - + DPRINT("Element in requested path is not a directory\n"); vfatReleaseFCB(pVCB, FCB); FCB = NULL; - *pParentFCB = NULL; - *pFCB = NULL; + status = STATUS_OBJECT_PATH_NOT_FOUND; + break; + } - return STATUS_OBJECT_PATH_NOT_FOUND; - } parentFCB = FCB; - if (prev < curr) + + /* Loop entry invariants */ +#if DBG + /* Previous points behind a path separator, Current... somewhere */ + ASSERT(Last == FileNameU.Buffer + FileNameU.Length / sizeof(WCHAR) - 1); + ASSERT(Previous > FileNameU.Buffer); + ASSERT(Previous[-1] == L'\\'); + ASSERT(Previous <= Current); + ASSERT(Previous <= Last); + ASSERT(Current <= Last); + + /* PartialName starts at FileNameU and ends at Current */ + ASSERT(PartialName.Buffer == FileNameU.Buffer); + if (Current != Previous || PartialName.Length == sizeof(WCHAR)) + ASSERT(PartialName.Length == (Current - FileNameU.Buffer) * sizeof(WCHAR)); + ASSERT(PartialName.MaximumLength == FileNameU.MaximumLength); + + /* Component starts at Previous and ends at Current */ + ASSERT(Component.Buffer == Previous); + ASSERT(Component.Length == (Current - Previous) * sizeof(WCHAR)); + ASSERT(Component.MaximumLength == FileNameU.MaximumLength - (Previous - FileNameU.Buffer) * sizeof(WCHAR)); +#endif + + /* Fix up the last path component to use the proper cased long name */ + if (Component.Length) { - Length = (curr - prev) * sizeof(WCHAR); - if (Length != parentFCB->LongNameU.Length) + ULONG LengthDifference; + PWCHAR NewCurrent; + + ASSERT(Current > Previous); + ASSERT(parentFCB->LongNameU.Length > 0); + ASSERT(PartialName.Length > sizeof(WCHAR)); + ASSERT(Current == Last + 1 || *Current == L'\\'); + + /* Were we using a short name before and need to move things? */ + if (Component.Length != parentFCB->LongNameU.Length) { - if (FileNameU.Length + parentFCB->LongNameU.Length - Length > FileNameU.MaximumLength) + /* Does the path still fit if we use the long name? */ + ASSERT(parentFCB->LongNameU.Length > Component.Length); + LengthDifference = parentFCB->LongNameU.Length - Component.Length; + if (FileNameU.Length + LengthDifference > FileNameU.MaximumLength) { + DPRINT1("Name too long: '%wZ' -> '%wZ'\n", &FileNameU, &parentFCB->PathNameU); vfatReleaseFCB(pVCB, parentFCB); - *pParentFCB = NULL; - *pFCB = NULL; - return STATUS_OBJECT_NAME_INVALID; + parentFCB = NULL; + status = STATUS_OBJECT_NAME_INVALID; + break; } - RtlMoveMemory(prev + parentFCB->LongNameU.Length / sizeof(WCHAR), curr, - FileNameU.Length - (curr - FileNameU.Buffer) * sizeof(WCHAR)); - FileNameU.Length += (USHORT)(parentFCB->LongNameU.Length - Length); - curr = prev + parentFCB->LongNameU.Length / sizeof(WCHAR); - last = FileNameU.Buffer + FileNameU.Length / sizeof(WCHAR) - 1; + + /* Move everything starting at Current forward */ + ASSERT(Current == Component.Buffer + Component.Length / sizeof(WCHAR)); + NewCurrent = Component.Buffer + parentFCB->LongNameU.Length / sizeof(WCHAR); + RtlMoveMemory(NewCurrent, + Current, + FileNameU.Length - (Current - FileNameU.Buffer) * sizeof(WCHAR)); + FileNameU.Length += LengthDifference; + PartialName.Length += LengthDifference; + Current = NewCurrent; + Last = FileNameU.Buffer + FileNameU.Length / sizeof(WCHAR) - 1; } - RtlCopyMemory(prev, parentFCB->LongNameU.Buffer, parentFCB->LongNameU.Length); + /* Write the canonical name for the last component */ + RtlCopyUnicodeString(&Component, &parentFCB->LongNameU); } - curr++; - prev = curr; - while (*curr != L'\\' && curr <= last) + else { - curr++; + ASSERT(Current == Previous); + //ASSERT(parentFCB->LongNameU.Length == 0); + //ASSERT(PartialName.Length == sizeof(WCHAR)); } - NameU.Buffer = FileNameU.Buffer; - NameU.Length = (curr - NameU.Buffer) * sizeof(WCHAR); - NameU.MaximumLength = FileNameU.MaximumLength; - DPRINT("%wZ\n", &NameU); - FCB = vfatGrabFCBFromTable(pVCB, &NameU); + + /* Same invariants, plus PartialName is now canonical */ +#if DBG + /* Previous points behind a path separator, Current... somewhere */ + ASSERT(Last == FileNameU.Buffer + FileNameU.Length / sizeof(WCHAR) - 1); + ASSERT(Previous > FileNameU.Buffer); + ASSERT(Previous[-1] == L'\\'); + ASSERT(Previous <= Current); + ASSERT(Previous <= Last); + ASSERT(Current <= Last); + + /* PartialName starts at FileNameU and ends at Current */ + ASSERT(PartialName.Buffer == FileNameU.Buffer); + if (Current != Previous || PartialName.Length == sizeof(WCHAR)) + ASSERT(PartialName.Length == (Current - FileNameU.Buffer) * sizeof(WCHAR)); + ASSERT(PartialName.MaximumLength == FileNameU.MaximumLength); + + /* Component starts at Previous and ends at Current */ + ASSERT(Component.Buffer == Previous); + ASSERT(Component.Length == (Current - Previous) * sizeof(WCHAR)); + ASSERT(Component.MaximumLength == FileNameU.MaximumLength - (Previous - FileNameU.Buffer) * sizeof(WCHAR)); + + /* PartialName is the exact name of the new parentFCB */ + FCB = vfatGrabFCBFromTable(pVCB, &PartialName); + ASSERT(FCB); + ASSERT(FCB == parentFCB); + if (FCB) + { + vfatReleaseFCB(pVCB, FCB); + FCB = NULL; + } +#endif + + if (Component.Length) + { + ASSERT(*Current = L'\\'); + ASSERT(Current > Previous); + Current++; + } + else + { + ASSERT(Current[-1] == L'\\'); + ASSERT(Current == Previous); + } + + Previous = Current; + while (*Current != L'\\' && Current <= Last) + { + Current++; + } + + Component.Buffer = Previous; + Component.Length = (Current - Previous) * sizeof(WCHAR); + Component.MaximumLength = FileNameU.MaximumLength - (Previous - FileNameU.Buffer) * sizeof(WCHAR); + + if (Component.Length == 0) + { + DPRINT1("Empty component in '%wZ'\n", &FileNameU); + vfatReleaseFCB(pVCB, parentFCB); + parentFCB = NULL; + status = STATUS_OBJECT_NAME_INVALID; + break; + } + + PartialName.Length = (Current - FileNameU.Buffer) * sizeof(WCHAR); + ASSERT(PartialName.Length <= FileNameU.Length); + + /* See if we have an existing FCB for this partial path */ + FCB = vfatGrabFCBFromTable(pVCB, &PartialName); if (FCB == NULL) { - NameU.Buffer = prev; - NameU.MaximumLength = NameU.Length = (curr - prev) * sizeof(WCHAR); - status = vfatDirFindFile(pVCB, parentFCB, &NameU, &FCB); - if (status == STATUS_OBJECT_NAME_NOT_FOUND) + /* We don't, so create new FCB for this component */ + ASSERT(parentFCB != NULL); + status = vfatDirFindFile(pVCB, parentFCB, &Component, &FCB); + if (!NT_SUCCESS(status)) { - *pFCB = NULL; - if (curr > last) + ASSERT(FCB == NULL); + if (status == STATUS_OBJECT_NAME_NOT_FOUND) { - *pParentFCB = parentFCB; - return STATUS_OBJECT_NAME_NOT_FOUND; + if (Current > Last) + { + break; + } + status = STATUS_OBJECT_PATH_NOT_FOUND; } - else - { - vfatReleaseFCB(pVCB, parentFCB); - *pParentFCB = NULL; - return STATUS_OBJECT_PATH_NOT_FOUND; - } + + vfatReleaseFCB(pVCB, parentFCB); + parentFCB = NULL; + break; } - else if (!NT_SUCCESS(status)) + } + + if (*pParentFCB) + { + if (RtlEqualUnicodeString(&(*pParentFCB)->PathNameU, + &PartialName, + TRUE)) { - vfatReleaseFCB(pVCB, parentFCB); - *pParentFCB = NULL; - *pFCB = NULL; - - return status; + ASSERT(FCB == *pParentFCB); } } } + if (TrailingBackslash && !vfatFCBIsDirectory(FCB)) + { + DPRINT1("Filename with trailing backslash is not a directory: '%wZ'\n", &FileNameU); + vfatReleaseFCB(pVCB, parentFCB); + parentFCB = NULL; + vfatReleaseFCB(pVCB, FCB); + FCB = NULL; + status = STATUS_OBJECT_NAME_INVALID; + } +Exit: + if (!NT_SUCCESS(status)) ASSERT(FCB == NULL); *pParentFCB = parentFCB; *pFCB = FCB; - return STATUS_SUCCESS; + return status; }