11package erofs
22
33import (
4+ "bufio"
45 "encoding/binary"
56 "errors"
67 "fmt"
@@ -84,14 +85,130 @@ func EroFS(r io.ReaderAt) (fs.FS, error) {
8485type image struct {
8586 sb disk.SuperBlock
8687
87- meta io.ReaderAt
88- blkPool sync.Pool
88+ meta io.ReaderAt
89+ blkPool sync.Pool
90+ longPrefixes []string // cached long xattr prefixes
91+ prefixesOnce sync.Once
92+ prefixesErr error
8993}
9094
9195func (img * image ) blkOffset () int64 {
9296 return int64 (img .sb .MetaBlkAddr ) << int64 (img .sb .BlkSizeBits )
9397}
9498
99+ // loadLongPrefixes loads and caches the long xattr prefixes from the packed inode
100+ // using the regular inode read logic to handle compressed/non-inline data.
101+ //
102+ // Long xattr name prefixes are used to optimize storage of xattrs with common
103+ // prefixes. They are stored sequentially in a special "packed inode" or
104+ // "meta inode".
105+ // See: https://docs.kernel.org/filesystems/erofs.html#extended-attributes
106+ func (img * image ) loadLongPrefixes () error {
107+ img .prefixesOnce .Do (func () {
108+ if img .sb .XattrPrefixCount == 0 {
109+ return
110+ }
111+
112+ // Long prefixes are stored in the packed inode at offset XattrPrefixStart * 4.
113+ // The packed inode (identified by PackedNid in the superblock) is a special
114+ // inode used for shared data and metadata.
115+ // We use ".packed" as a descriptive name for this internal inode.
116+ f := & file {
117+ img : img ,
118+ name : ".packed" ,
119+ inode : img .sb .PackedNid ,
120+ ftype : 0 , // regular file
121+ }
122+
123+ // Read inode info to determine size and layout
124+ fi , err := f .readInfo (false )
125+ if err != nil {
126+ img .prefixesErr = fmt .Errorf ("failed to read packed inode: %w" , err )
127+ return
128+ }
129+
130+ // Calculate the starting offset. XattrPrefixStart is defined in the
131+ // superblock as being in units of 4 bytes from the start of the
132+ // packed inode's data.
133+ startOffset := int64 (img .sb .XattrPrefixStart ) * 4
134+ if startOffset > fi .size {
135+ img .prefixesErr = fmt .Errorf ("xattr prefix start offset %d exceeds packed inode size %d" , startOffset , fi .size )
136+ return
137+ }
138+
139+ // Set the read offset
140+ f .offset = startOffset
141+
142+ r := bufio .NewReader (f )
143+ img .longPrefixes = make ([]string , img .sb .XattrPrefixCount )
144+
145+ for i := 0 ; i < int (img .sb .XattrPrefixCount ); i ++ {
146+ // Each long prefix entry consists of:
147+ // - A 2-byte little-endian length field (prefixLen)
148+ // - A 1-byte base_index (short xattr prefix)
149+ // - The infix string of length prefixLen - 1
150+ // - Padding to align the entire entry (2 + prefixLen) to a 4-byte boundary.
151+ var lenBuf [2 ]byte
152+ if _ , err := io .ReadFull (r , lenBuf [:]); err != nil {
153+ img .prefixesErr = fmt .Errorf ("failed to read long xattr prefix length at index %d: %w" , i , err )
154+ return
155+ }
156+ prefixLen := int (binary .LittleEndian .Uint16 (lenBuf [:]))
157+
158+ if prefixLen < 1 {
159+ img .prefixesErr = fmt .Errorf ("invalid long xattr prefix length %d at index %d" , prefixLen , i )
160+ return
161+ }
162+
163+ // Read data (base_index + infix)
164+ data := make ([]byte , prefixLen )
165+ if _ , err := io .ReadFull (r , data ); err != nil {
166+ img .prefixesErr = fmt .Errorf ("failed to read long xattr prefix data at index %d: %w" , i , err )
167+ return
168+ }
169+
170+ // First byte is the base_index referencing a standard xattr prefix
171+ baseIndex := xattrIndex (data [0 ])
172+
173+ // Remaining bytes are the infix to be appended to the base prefix
174+ infix := string (data [1 :])
175+
176+ // Construct full prefix: base prefix + infix
177+ img .longPrefixes [i ] = baseIndex .String () + infix
178+
179+ // Align to 4-byte boundary. The entry starts with a 2-byte length field
180+ // followed by prefixLen bytes of data.
181+ totalLen := 2 + prefixLen
182+ if rem := totalLen % 4 ; rem != 0 {
183+ padding := 4 - rem
184+ if _ , err := r .Discard (padding ); err != nil {
185+ // If we are at the last prefix and hit EOF, it's acceptable if the file ends without padding
186+ if i == int (img .sb .XattrPrefixCount )- 1 && errors .Is (err , io .EOF ) {
187+ return
188+ }
189+ img .prefixesErr = fmt .Errorf ("failed to discard padding at index %d: %w" , i , err )
190+ return
191+ }
192+ }
193+ }
194+ })
195+
196+ return img .prefixesErr
197+ }
198+
199+ // getLongPrefix returns the long xattr prefix at the given index
200+ func (img * image ) getLongPrefix (index uint8 ) (string , error ) {
201+ if err := img .loadLongPrefixes (); err != nil {
202+ return "" , err
203+ }
204+
205+ if int (index ) >= len (img .longPrefixes ) {
206+ return "" , fmt .Errorf ("long xattr prefix index %d out of range (max %d)" , index , len (img .longPrefixes )- 1 )
207+ }
208+
209+ return img .longPrefixes [index ], nil
210+ }
211+
95212func (img * image ) loadAt (addr , size int64 ) (* block , error ) {
96213 blkSize := int64 (1 << img .sb .BlkSizeBits )
97214 if size > blkSize {
@@ -647,4 +764,4 @@ func decodeSuperBlock(b [disk.SizeSuperBlock]byte, sb *disk.SuperBlock) error {
647764 return fmt .Errorf ("invalid super block: invalid magic number %x" , sb .MagicNumber )
648765 }
649766 return nil
650- }
767+ }
0 commit comments