SquashFS: root as an extended directory inode, symlink targets, block starts computed once - #79
Conversation
… starts computed once - The reader cast the root inode to the basic directory type; mksquashfs stores a directory whose table exceeds one metadata block (the root included) as an extended directory inode with an index, so such images failed to open. - Symlink.TargetPath threw NotImplementedException, so any path lookup that landed on a symbolic link threw. The target is the UTF-8 path stored after the inode, relative to the link's directory unless absolute. - FileContentBuffer walked the block sizes from the first block on every read (O(blocks) per read; a random read near the end of a large file summed every size before it). The block starts are computed once when the file is opened. Tests: big-root.sqsh, built by mksquashfs at test time (1,000 files in the root and a symlink; no embedded fallback, so the tests are no-ops without the tool). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
The overall direction looks good to me, particularly the extended-root handling and the block-start precomputation. Since this implementation was AI-assisted, here is a focused follow-up that you can feed back to Claude before I do a final review: Please review this PR again specifically for completeness and robustness of the new SquashFS symlink support.
After making any changes, summarize:
Please leave the PR as draft for maintainer review. |
…d test images - ExtendedSymlinkInode (type 10): the basic inode's fixed part and target path, then a 32-bit xattr index after the target, which Symlink.TargetPath reads so the whole inode is accounted for. mksquashfs writes this form only for a symlink with extended attributes, so the reader no longer refuses an image with an xattr table (nothing in it reads the table; the extended inodes skip their index). - SymlinkSize is checked against 65536 (mksquashfs reads a link into a 65536-byte buffer and refuses a longer one; the Linux driver rejects a target longer than a page) before allocating, and the target must lie within the inode table (MetablockReader.CurrentBlockStart stays before the directory table). - Tests run on embedded images where mksquashfs is absent, like the existing fixtures: big-root.sqsh and xattr-symlink.sqsh embedded, extended-inodes.sqsh re-embedded with mixed.bin (a compressed, a stored, a sparse and a compressed block, then a fragment) for the block-start calculation. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Thanks, this now looks very close to ready for merge. The extended symlink handling, embedded fixtures, and mixed block-layout regression test address the issues I raised earlier. I found one small correctness issue that I would like fixed before merging: "Symlink.MaxTargetLength" is currently "65536", but upstream SquashFS defines "SQUASHFS_SYMLINK_MAX" as "65535". Since this check is intended to validate the on-disk format, please use the actual format limit rather than the size of the temporary buffer used by tooling. You can feed this back to Claude: Please make one final narrow cleanup to PR #79:
Unless that re-check finds something unexpected, I do not currently see any other substantive blocker. |
|
On it ! Btw I really appreciate your openness and responsiveness on this repo! |
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
c4c4ac8
into
LTRData:LTRData.DiscUtils-initial
|
Thanks a lot for your contribution! |
Things found while switching an application from its own SquashFS reader to DiscUtils after #78:
VfsSquashFileSystemReadercast the root inode toDirectoryInode. mksquashfs stores a directory whose table exceeds one metadata block as an extended directory inode (with an index), the root included, so an image with a large root directory failed to open withInvalidCastException.Directoryalready accepts either kind throughIDirectoryInode; the cast is gone.Symlink.TargetPaththrewNotImplementedException, and since the VFS layer resolves a symlink at every path segment, any lookup that landed on a symbolic link threw. The target is the UTF-8 path stored right after the inode (SymlinkInode.SymlinkSizebytes), relative to the link's directory unless absolute, which is whatResolveSymlinkexpects. The length is validated against the format'sSQUASHFS_SYMLINK_MAX(65535) before anything is allocated, and the target must lie within the inode table.FileContentBufferwalked the block size list from the first block on every read to find where the target block sits on disk: O(blocks) per read, so a random read near the end of a large file summed every size before it (a 96 GB file with 1 MiB blocks: ~98k additions per read). The starts are now computed once when the file is opened (alongper block); the mask is the format's low 24 bits.Tests:
big-root.sqsh(1,000 files in the root and a symlink) andxattr-symlink.sqsh(a symlink with an attribute, made with-xattrs-add, so an extended symlink inode and an xattr table) are added to theSquashFixturesrecipe, andextended-inodes.sqshgainsmixed.bin(a compressed, a stored, a sparse and a compressed block, then a fragment) for the block-start calculation. Like the existing fixtures, the images are built fresh by mksquashfs where it is present (the ubuntu runner) and come from embedded copies, made by squashfs-tools 4.7.5 from the same recipe, elsewhere, so every test executes on every runner. Verified with 4.7.5, with 4.6.1 built from source, and with no mksquashfs on PATH.🤖 Generated with Claude Code