@@ -84,14 +84,140 @@ func EroFS(r io.ReaderAt) (fs.FS, error) {
8484type image struct {
8585 sb disk.SuperBlock
8686
87- meta io.ReaderAt
88- blkPool sync.Pool
87+ meta io.ReaderAt
88+ blkPool sync.Pool
89+ longPrefixes []string // cached long xattr prefixes
90+ prefixesOnce sync.Once
91+ prefixesErr error
8992}
9093
9194func (img * image ) blkOffset () int64 {
9295 return int64 (img .sb .MetaBlkAddr ) << int64 (img .sb .BlkSizeBits )
9396}
9497
98+ // loadLongPrefixes loads and caches the long xattr prefixes from the superblock
99+ func (img * image ) loadLongPrefixes () error {
100+ img .prefixesOnce .Do (func () {
101+ if img .sb .XattrPrefixCount == 0 {
102+ return
103+ }
104+
105+ // Long prefixes are stored in the packed inode, after the inode header
106+ // The packed inode is typically a compact inode (32 bytes)
107+ // Address = MetaBlkAddr + (PackedNid * 32) + inode_size + (XattrPrefixStart * 4)
108+ // Note: XattrPrefixStart is in units of 4 bytes from the end of the packed inode
109+ baseAddr := img .blkOffset () + int64 (img .sb .PackedNid )* 32 + 32 + int64 (img .sb .XattrPrefixStart )* 4
110+
111+ img .longPrefixes = make ([]string , img .sb .XattrPrefixCount )
112+
113+ // We'll read the prefixes incrementally since they can be large
114+ // and may span multiple blocks
115+ currentAddr := baseAddr
116+ var blk * block
117+ var buf []byte
118+ var err error
119+ bufOffset := 0 // offset within the current buffer
120+
121+ for i := 0 ; i < int (img .sb .XattrPrefixCount ); i ++ {
122+ // Ensure we have at least 2 bytes to read the length
123+ if blk == nil || bufOffset + 2 > len (buf ) {
124+ if blk != nil {
125+ img .putBlock (blk )
126+ }
127+ blk , err = img .loadAt (currentAddr , int64 (1 << img .sb .BlkSizeBits ))
128+ if err != nil {
129+ img .prefixesErr = fmt .Errorf ("failed to load long xattr prefix data at index %d: %w" , i , err )
130+ return
131+ }
132+ buf = blk .bytes ()
133+ bufOffset = 0
134+ }
135+
136+ // Read length (little endian uint16) - includes base_index byte + infix bytes
137+ prefixLen := int (binary .LittleEndian .Uint16 (buf [bufOffset : bufOffset + 2 ]))
138+ bufOffset += 2
139+ currentAddr += 2
140+
141+ if prefixLen < 1 {
142+ if blk != nil {
143+ img .putBlock (blk )
144+ }
145+ img .prefixesErr = fmt .Errorf ("invalid long xattr prefix length %d at index %d" , prefixLen , i )
146+ return
147+ }
148+
149+ // Check if we have enough data for the prefix in current buffer
150+ if bufOffset + prefixLen > len (buf ) {
151+ // Need to read more data - reload from current position
152+ if blk != nil {
153+ img .putBlock (blk )
154+ }
155+ // Load enough to cover this prefix
156+ loadSize := int64 (prefixLen )
157+ if loadSize < int64 (1 << img .sb .BlkSizeBits ) {
158+ loadSize = int64 (1 << img .sb .BlkSizeBits )
159+ }
160+ blk , err = img .loadAt (currentAddr , loadSize )
161+ if err != nil {
162+ img .prefixesErr = fmt .Errorf ("failed to load long xattr prefix data for index %d: %w" , i , err )
163+ return
164+ }
165+ buf = blk .bytes ()
166+ bufOffset = 0
167+
168+ // Verify we have enough now
169+ if prefixLen > len (buf ) {
170+ img .putBlock (blk )
171+ img .prefixesErr = fmt .Errorf ("long xattr prefix at index %d too large (%d bytes)" , i , prefixLen )
172+ return
173+ }
174+ }
175+
176+ // First byte is the base_index
177+ baseIndex := xattrIndex (buf [bufOffset ])
178+ bufOffset ++
179+ currentAddr ++
180+ prefixLen --
181+
182+ // Remaining bytes are the infix
183+ infix := string (buf [bufOffset : bufOffset + prefixLen ])
184+ bufOffset += prefixLen
185+ currentAddr += int64 (prefixLen )
186+
187+ // Construct full prefix: base prefix + infix
188+ img .longPrefixes [i ] = baseIndex .String () + infix
189+
190+ // Align to 4-byte boundary
191+ // Total length field (2 bytes) + data (base_index + infix)
192+ totalLen := 2 + 1 + prefixLen
193+ if rem := totalLen % 4 ; rem != 0 {
194+ padding := 4 - rem
195+ bufOffset += padding
196+ currentAddr += int64 (padding )
197+ }
198+ }
199+
200+ if blk != nil {
201+ img .putBlock (blk )
202+ }
203+ })
204+
205+ return img .prefixesErr
206+ }
207+
208+ // getLongPrefix returns the long xattr prefix at the given index
209+ func (img * image ) getLongPrefix (index uint8 ) (string , error ) {
210+ if err := img .loadLongPrefixes (); err != nil {
211+ return "" , err
212+ }
213+
214+ if int (index ) >= len (img .longPrefixes ) {
215+ return "" , fmt .Errorf ("long xattr prefix index %d out of range (max %d)" , index , len (img .longPrefixes )- 1 )
216+ }
217+
218+ return img .longPrefixes [index ], nil
219+ }
220+
95221func (img * image ) loadAt (addr , size int64 ) (* block , error ) {
96222 blkSize := int64 (1 << img .sb .BlkSizeBits )
97223 if size > blkSize {
0 commit comments