Skip to content

Commit 2b43857

Browse files
committed
Add support for long xattr prefixes
Signed-off-by: Derek McGowan <derek@mcg.dev>
1 parent 5465956 commit 2b43857

File tree

3 files changed

+143
-9
lines changed

3 files changed

+143
-9
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ could provide an interface to create erofs files as well.
1414
- [x] Read erofs files created with default `mkfs.erofs` options
1515
- [x] Read chunk-based erofs files (without indexes)
1616
- [x] Xattr support
17-
- [ ] Long xattr prefix support
17+
- [x] Long xattr prefix support
1818
- [ ] Read erofs files with compression
1919
- [ ] Extra devices for chunked data and chunk indexes
2020
- [ ] Creating erofs files

erofs.go

Lines changed: 128 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,14 +84,140 @@ func EroFS(r io.ReaderAt) (fs.FS, error) {
8484
type 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

9194
func (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+
95221
func (img *image) loadAt(addr, size int64) (*block, error) {
96222
blkSize := int64(1 << img.sb.BlkSizeBits)
97223
if size > blkSize {

xattr.go

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -88,9 +88,13 @@ func setXattrs(b *file, addr int64, blk *block) (err error) {
8888
sb = sb[disk.SizeXattrEntry:]
8989
var prefix string
9090
if xattrEntry.NameIndex&0x80 == 0x80 {
91-
//nameIndex := xattrEntry.NameIndex & 0x7F
92-
// TODO: Get long prefix
93-
return fmt.Errorf("shared xattr with long prefix not implemented for nid %d", b.inode)
91+
// Long prefix: highest bit set
92+
longPrefixIndex := xattrEntry.NameIndex & 0x7F
93+
var err error
94+
prefix, err = b.img.getLongPrefix(longPrefixIndex)
95+
if err != nil {
96+
return fmt.Errorf("failed to get long prefix for shared xattr nid %d: %w", b.inode, err)
97+
}
9498
} else if xattrEntry.NameIndex != 0 {
9599
prefix = xattrIndex(xattrEntry.NameIndex).String()
96100
}
@@ -130,9 +134,13 @@ func setXattrs(b *file, addr int64, blk *block) (err error) {
130134
xb = xb[disk.SizeXattrEntry:]
131135
var prefix string
132136
if xattrEntry.NameIndex&0x80 == 0x80 {
133-
//nameIndex := xattrEntry.NameIndex & 0x7F
134-
// TODO: Get long prefix
135-
return fmt.Errorf("shared xattr with long prefix not implemented for nid %d", b.inode)
137+
// Long prefix: highest bit set
138+
longPrefixIndex := xattrEntry.NameIndex & 0x7F
139+
var err error
140+
prefix, err = b.img.getLongPrefix(longPrefixIndex)
141+
if err != nil {
142+
return fmt.Errorf("failed to get long prefix for inline xattr nid %d: %w", b.inode, err)
143+
}
136144
} else if xattrEntry.NameIndex != 0 {
137145
prefix = xattrIndex(xattrEntry.NameIndex).String()
138146
}

0 commit comments

Comments
 (0)