Fix five regressions on LTRData.DiscUtils-initial (registry, NTFS, dynamic-disk hang, sparse-stream)#69
Conversation
…xed ri/li lists SubKeyIndirectListCell backs both "ri" index-root lists (entries are subordinate lists) and "li" leaf-index lists (entries are key nodes). The read paths (Count, EnumerateKeyNames, EnumerateKeys, DoFindKey, KeyFinder) already tolerate a single list whose entries are a mix of sublists and key nodes, by dispatching on the actual cell type. But LinkSubKey/UnlinkSubKey still branched on ListType and hard-cast every entry (GetCell<ListCell> for "ri", GetCell<KeyNodeCell> for "li"). On a real Windows SYSTEM hive whose "ri" list contains a direct key-node entry, deleting a subkey (UnlinkSubKey) threw: System.InvalidCastException: Unable to cast object of type 'DiscUtils.Registry.KeyNodeCell' to type 'DiscUtils.Registry.ListCell'. Reading/enumerating the same key worked (those paths were already hardened); only add/delete crashed. Fix: LinkSubKey/UnlinkSubKey now dispatch on the actual cell type per entry (GetCell<Cell> + "is ListCell"), exactly as the read paths do. Behaviour for homogeneous "ri"/"li" lists is unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Includes the cherry-picked SubKeyIndirectListCell fix (InvalidCastException on deleting/adding keys in mixed ri/li subkey lists). Triggers a fork build so DevePXEBoot can consume the registry delete fix before it lands upstream (PR LTRData#69). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Registry hive cells are 8-byte aligned, so AllocateCell must reject sizes whose low 3 bits are set: (size & 0x7) != 0. Commit f7bf755 ("Minor code cleanups and optimizations") changed this from `size % 8 != 0` to `(size % 0x7) != 0` (mod 7), which rejects ~6 of every 7 valid sizes - including the ~80-byte root key cell - so RegistryHive.Create() throws "Invalid cell size" and every registry/BootConfig test fails at construction (and the test run hangs). Restores the 8-byte alignment check. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Note on fix #2 ( That is also where CI on |
|
Oh that was a good find! I have not noticed that CI fails now because I did not get any notifications about it. I get notifications about it from other repositories though. Weird. I need to check what is happening here! |
|
Yeah, my AI found it 😄 Also have a good review of the other fix I did as it's also AI authored and I don't really have the knowledge on this part to see if it makes sense. |
|
Hmm it does seem the build is still failing even after this fix. I'll see if I can figure out what's going on. |
|
Yes, the bugs were mostly introduced by some AI optimizations. Though, they feel in most cases safe as long as CI tests still complete successfully. Which I thought they did. 😅 But I think I also need to add CI tests for Linux. There have been several cases lately where tests work in Windows but fall when I run them in Linux and need to address something. Usually just the rest itself, but sometimes it reveals issues deeper in the library. |
…ies (regressed in a551635) GetFileLength must honor the NtfsOptions.FileLengthFromDirectoryEntries option: when set (the default), a default-data-stream length request returns the directory entry's cached RealSize. Commit a551635 ("Bugfixes for FileExist and GetFileLength") removed that shortcut, so GetFileLength always looked up the data attribute and the option became a no-op - breaking NtfsFileSystemTest.GetFileLength (and the async variant), which expects a hardlink's stale directory-entry size (14325) by default and the live data-attribute size (50) only when the option is disabled. Restores the shortcut. The FileExists fix in that commit is correct and left intact. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Note on fix #3 (NTFS if (NtfsOptions.FileLengthFromDirectoryEntries && attributeName == null && attributeType == AttributeType.Data)
{
return (long)dirEntry.Details.RealSize;
}That made the |
FindNextPresentSector/FindNextAbsentSector advance with pos += (8 - sectorInBlock & 0x7) * Sizes.Sector. C# precedence makes this (8 - sectorInBlock) & 0x7, not 8 - (sectorInBlock & 0x7): when sectorInBlock is byte-aligned and the block-bitmap byte is empty, the original (8 - sectorInBlock % 8) advanced by 8 sectors, but the rewrite yields 8 & 7 = 0, so pos never advances and the extent enumeration (LayerExtents/Extents and the dynamic VHD/VMDK builders) loops forever. Commit f7bf755 ("Minor code cleanups and optimizations") introduced this by rewriting % 8 to & 0x7 without parentheses. Parenthesized so the mask applies to sectorInBlock (the 7 - x & 0x7 mask forms are coincidentally equivalent but were parenthesized too for clarity). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…b5b75d) The synchronized wrapper kept its own Position auto-property and set content.Position = Position before each operation but never wrote the advanced position back afterward, so Position stayed frozen and reads/writes through the wrapper re-processed the same offset (e.g. ExFat ReadLongSparseLimited read the wrong data). Delegate Position to content.Position under the lock - correct for this single-shared- cursor synchronized wrapper and fixes every read/write path (sync and async) at once. Introduced by 4b5b75d ("Synchronized wrappers for disks and streams"). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Introducing commits for fixes #4 and #5:
For #4 specifically, note the |
|
The build now succeeds so it all seems to work 😄 |
43df6ed
into
LTRData:LTRData.DiscUtils-initial
|
I merged the commits with some changes to avoid reintroducing some bugs that were fixed previously. I also added timeouts to CI tests to avoid this scenario in the future where new bugs that hang unit tests go unnoticed because they never finish and never report failure. Closing this PR. Thanks a lot for finding and solving these bugs! |
Fixes five regressions currently on
LTRData.DiscUtils-initial. With all five, the fullLibraryTestssuite passes (581 passed, 1 skipped, 0 failed, no hangs). Without them the suite cannot build/create a hive (#2), fails NTFS/ExFat reads (#3, #5), and hangs indefinitely (#4).1.
SubKeyIndirectListCell—InvalidCastExceptionon mixedri/lilistsLinkSubKey/UnlinkSubKeybranched onListTypeand hard-cast every entry, but a list can contain a mix of sublists and key nodes (the read paths already tolerate this). Deleting/adding a subkey under such a list threwInvalidCastException: KeyNodeCell -> ListCell. Fixed by dispatching per entry on the actual cell type, mirroring the read paths.2.
Bin.AllocateCell— 8-byte alignment check regressed to mod 7Cells are 8-byte aligned, so the check must be
(size & 0x7) != 0. It had been changed to(size % 0x7) != 0(mod 7), rejecting ~6 of every 7 valid sizes including the ~80-byte root cell, soRegistryHive.Create()itself throwsInvalid cell size. Restored to& 0x7. (Introducing commit in a comment below.)3.
NtfsFileSystem.GetFileLength—FileLengthFromDirectoryEntriesoption ignoredThe default-data-stream shortcut returning the directory entry's
RealSizewas removed, making the option a no-op and breakingNtfsFileSystemTest.GetFileLength. Restored the shortcut. (Introducing commit in a comment below.)4.
DynamicStream— infinite loop in sector scan (the hang)FindNextPresentSector/FindNextAbsentSectoradvance withpos += (8 - sectorInBlock & 0x7) * Sizes.Sector. By C# precedence that is(8 - sectorInBlock) & 0x7, not8 - (sectorInBlock & 0x7): at a byte-aligned, empty bitmap byte the original8 - sectorInBlock % 8advanced by 8 sectors, but the rewrite yields8 & 7 = 0, soposnever advances and extent enumeration / the dynamic VHD+VMDK builders loop forever. Parenthesized to restore the semantics. (Introducing commit in a comment below.)5.
SynchronizedSparseStream—Positionnever advancesThe synchronized wrapper set
content.Position = Positionbefore each op but never wrote the advanced position back, soPositionstayed frozen and reads/writes through the wrapper re-processed the same offset (e.g.ExFat...ReadLongSparseLimited).Positionnow delegates tocontent.Positionunder the lock, fixing every read/write path at once. (Introducing commit in a comment below.)