Merge pull request #14499 from containerd/dependabot/go_modules/github.com/erofs/go-erofs-0.3.2

build(deps): bump github.com/erofs/go-erofs from 0.3.1 to 0.3.2
This commit is contained in:
Maksym Pavlenko authored and GitHub committed 2026-11-04 18:07:16 +00:00
commit 4ba2e4619e
13 files changed
+791 -330

No files matched your search

+1 -1
View File
@@ -37,7 +37,7 @@ require (
github.com/docker/go-events v0.1.0
github.com/docker/go-metrics v0.1.0
github.com/docker/go-units v0.5.0
github.com/erofs/go-erofs v0.3.1
github.com/erofs/go-erofs v0.3.2
github.com/fsnotify/fsnotify v1.10.1
github.com/google/certtostore v1.0.7
github.com/google/go-cmp v0.7.0
+2 -2
View File
@@ -100,8 +100,8 @@ github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes=
github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/erofs/go-erofs v0.3.1 h1:Sux82Jq9yvyYhIoLgSHDp741p/+370HsOj9dAh1+VVs=
github.com/erofs/go-erofs v0.3.1/go.mod h1:XkSeN9MHszGd4+3gcEjadJLYHCQpWzJ7/8yznzMuzJs=
github.com/erofs/go-erofs v0.3.2 h1:GBWc9wyDOBVMLpA9OC0MkFfs4AbiEAAEhNH8SpQwD/4=
github.com/erofs/go-erofs v0.3.2/go.mod h1:XkSeN9MHszGd4+3gcEjadJLYHCQpWzJ7/8yznzMuzJs=
github.com/felixge/httpsnoop v1.1.0 h1:3YtUj32ZZkqZtt3sZZsClsymw/QDuVfpNhoA31zeORc=
github.com/felixge/httpsnoop v1.1.0/go.mod h1:Zqxgdd+1Rkcz8euOqdr7lqgCRJztwr5hp9vDSi5UZCE=
github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho=
+10 -2
View File
@@ -66,6 +66,11 @@ var (
// a directory.
ErrIsDirectory = errors.New("is a directory")
// ErrDirNotEmpty is returned by Remove when the named path is a
// non-empty directory. Mirrors the behavior of os.Remove (which returns
// ENOTEMPTY).
ErrDirNotEmpty = errors.New("directory not empty")
// ErrLoop is returned when too many symlinks are encountered during
// path resolution.
ErrLoop = fmt.Errorf("too many symlinks: %w", ErrInvalid)
@@ -83,6 +88,9 @@ var (
// InodeInfo: Ino() uint64, Nlink() uint64
// DeviceInfo: Rdev() uint64
// Xattrs: GetAllXattr() map[string]string, GetXattr(string) (string, bool)
//
// Mode has no accessor interface: use [fs.FileInfo.Mode], which returns the
// same value, including the setuid, setgid and sticky bits.
type Stat struct {
Mode fs.FileMode
Size int64
@@ -1005,7 +1013,7 @@ func (b *file) readInfo() (ino *inode, err error) {
inodeLayout: layout,
inodeData: di.InodeData,
size: int64(di.Size),
mode: (fs.FileMode(di.Mode) & ^fs.ModeType) | b.ftype,
mode: (disk.EroFSModeToGoFileMode(di.Mode) & ^fs.ModeType) | b.ftype,
rawMode: di.Mode,
uid: uint32(di.UID),
gid: uint32(di.GID),
@@ -1026,7 +1034,7 @@ func (b *file) readInfo() (ino *inode, err error) {
inodeLayout: layout,
inodeData: di.InodeData,
size: int64(di.Size),
mode: (fs.FileMode(di.Mode) & ^fs.ModeType) | b.ftype,
mode: (disk.EroFSModeToGoFileMode(di.Mode) & ^fs.ModeType) | b.ftype,
rawMode: di.Mode,
uid: di.UID,
gid: di.GID,
+2 -2
View File
@@ -14,8 +14,8 @@ var xattrPrefixes = [...]struct {
prefix string
}{
{1, "user."},
{2, "system.posix_acl_access."},
{3, "system.posix_acl_default."},
{2, "system.posix_acl_access"},
{3, "system.posix_acl_default"},
{4, "trusted."},
{5, "lustre."},
{6, "security."},
+9
View File
@@ -17,6 +17,15 @@ type Entry struct {
Chunks []Chunk // physical block refs (metadata-only mode)
Contiguous bool // data blocks are contiguous; flat-plain is sufficient
MetadataOnly bool // chunk-based layout even without chunks
// Dev and Ino identify the source inode (e.g. syscall.Stat_t.Dev/Ino, or
// the NID within a source EROFS image). When Nlink > 1, the writer uses
// (Dev, Ino) to detect additional names that refer to the same source
// file and share a single fsInode for them, mirroring an explicit Link()
// call. Ino == 0 means "unknown"; such entries never participate in hard
// link detection. Inode numbers are only unique per device, so Dev must
// be included in the comparison.
Dev, Ino uint64
}
// NullPhysicalBlock is the sentinel value for Chunk.PhysicalBlock that marks
+59 -24
View File
@@ -26,17 +26,27 @@ func (w *erofsWriter) planLayout(root *erofsEntry) {
}
walk(root)
w.totalInodes = uint64(len(w.entries))
// Secondary hard-link entries share the primary's inode; count only primaries.
var inodeCount uint64
for _, e := range w.entries {
if e.hardLinkPrimary == nil {
inodeCount++
}
}
w.totalInodes = inodeCount
// Block 0 holds: 1024-byte pad + 128-byte superblock + device slot(s) + padding
// MetaBlkAddr is set later by write() depending on the on-disk layout.
// Assign NIDs sequentially.
// NID = byte offset from metaStartPos / 32.
// Each extended inode is 64 bytes = 2 NID slots.
// Trailing data follows and is padded to 32-byte boundary.
// Assign NIDs sequentially. NID = byte offset from metaStartPos / 32.
// Secondary hard-link entries are skipped here; a second pass copies the
// primary's finalized NID to them.
currentOff := 0 // byte offset from metaStartPos
for _, e := range w.entries {
if e.hardLinkPrimary != nil {
continue // skip; NID assigned in second pass below
}
e.nid = uint64(currentOff / 32)
e.xattrSize = calcXattrSize(e)
@@ -67,7 +77,7 @@ func (w *erofsWriter) planLayout(root *erofsEntry) {
}
default:
// Full-image mode: decide inline vs plain
if int(e.size) <= w.blockSize-headerSize {
if !w.noInlineData && int(e.size) <= w.blockSize-headerSize {
inBlockOff := (currentOff + headerSize) % w.blockSize
if inBlockOff+int(e.size) <= w.blockSize {
e.layout = disk.LayoutFlatInline
@@ -100,8 +110,9 @@ func (w *erofsWriter) planLayout(root *erofsEntry) {
// Recalculate trailing size now that layout is decided
e.trailingSize = w.calcTrailingSize(e)
e.chunkPad = chunkIndexPad(e)
totalInodeSize := headerSize + e.trailingSize
totalInodeSize := headerSize + e.chunkPad + e.trailingSize
// Pad to 32-byte boundary
if totalInodeSize%32 != 0 {
totalInodeSize = (totalInodeSize + 31) & ^31
@@ -122,7 +133,8 @@ func (w *erofsWriter) planLayout(root *erofsEntry) {
// Fall back to flat-plain (data would cross block boundary)
e.layout = disk.LayoutFlatPlain
e.trailingSize = w.calcTrailingSize(e)
totalInodeSize = headerSize + e.trailingSize
e.chunkPad = chunkIndexPad(e)
totalInodeSize = headerSize + e.chunkPad + e.trailingSize
if totalInodeSize%32 != 0 {
totalInodeSize = (totalInodeSize + 31) & ^31
}
@@ -132,9 +144,36 @@ func (w *erofsWriter) planLayout(root *erofsEntry) {
currentOff += totalInodeSize
}
// Second pass: copy the primary's finalized NID to secondary entries.
for _, e := range w.entries {
if e.hardLinkPrimary != nil {
e.nid = e.hardLinkPrimary.nid
}
}
w.rootNid = root.nid
}
// chunkIndexPad returns the padding inserted between an inode's xattr area and
// its chunk-index map so the map begins aligned to the chunk-index unit, as the
// kernel and reader both expect:
//
// pos = ALIGN(iloc + inode_isize + xattr_isize, unit) + unit*chunknr
//
// (see fs/erofs/data.c and loadBlock's LayoutChunkBased case). inodeCoreSize is
// always a multiple of the unit, so only the xattr area can push the map off
// alignment. Non-chunk layouts (inline data) are read without this alignment
// and so must not be padded.
func chunkIndexPad(e *erofsEntry) int {
if e.layout != disk.LayoutChunkBased || e.trailingSize == 0 {
return 0
}
if r := (inodeCoreSize(e) + e.xattrSize) % disk.SizeChunkIndex; r != 0 {
return disk.SizeChunkIndex - r
}
return 0
}
// calcTrailingSize returns the number of bytes following the 64-byte inode.
func (w *erofsWriter) calcTrailingSize(e *erofsEntry) int {
switch e.mode & disk.StatTypeMask {
@@ -166,29 +205,25 @@ func (w *erofsWriter) calcTrailingSize(e *erofsEntry) int {
}
}
// direntNames returns the sorted list of dirent names for a directory,
// including "." and "..". EROFS requires dirents within each block to
// be sorted alphabetically.
func direntNames(e *erofsEntry) []string {
// direntDataSize calculates the serialized EROFS dirent data size for a directory.
// For multi-block directories, this includes inter-block padding.
func (w *erofsWriter) direntDataSize(e *erofsEntry) int {
if len(e.children) == 0 {
// Empty dir still needs "." and ".." entries.
return 2*disk.SizeDirent + 1 + 2
}
// Build sorted name lengths matching the order writeDirents produces.
// "." and ".." are not guaranteed to sort before all real names (any
// name with an ASCII value below '.' such as '-' sorts before them).
names := make([]string, 0, len(e.children)+2)
names = append(names, ".", "..")
for _, c := range e.children {
names = append(names, c.name)
}
sort.Strings(names)
return names
}
// direntDataSize calculates the serialized EROFS dirent data size for a directory.
// For multi-block directories, this includes inter-block padding.
func (w *erofsWriter) direntDataSize(e *erofsEntry) int {
names := direntNames(e)
nEntries := len(names)
if len(e.children) == 0 {
// Empty dir still needs "." and ".." entries
return 2*disk.SizeDirent + 1 + 2
}
totalSize := 0
i := 0
for i < nEntries {
@@ -209,7 +244,7 @@ func (w *erofsWriter) direntDataSize(e *erofsEntry) int {
blockUsed = disk.SizeDirent + len(names[i])
i++
}
// Pad non-final blocks to block boundary
// Pad non-final blocks to block boundary.
if i < nEntries && blockUsed%w.blockSize != 0 {
blockUsed = (blockUsed + w.blockSize - 1) & ^(w.blockSize - 1)
}
+532 -188
View File
File diff suppressed because it is too large. Load diff
+2
View File
@@ -19,6 +19,8 @@ func entryFromSys(info fs.FileInfo) *builder.Entry {
MtimeNs: uint32(sys.Mtimespec.Nsec),
Nlink: uint32(sys.Nlink),
Rdev: uint32(sys.Rdev),
Dev: uint64(sys.Dev),
Ino: sys.Ino,
}
default:
return nil
+104 -86
View File
@@ -66,7 +66,9 @@ type imgQEntry struct {
// syscalls), it reads the entire metadata area into memory and parses
// inodes, directory entries, xattrs, and chunk indexes directly from the
// buffer. This reduces thousands of syscalls to a single ReadAt.
func (fsys *Writer) copyFromImage(img *image) error {
// hardlinks is the current CopyFrom call's (Dev, Ino) → fsInode map, used to
// detect additional names that refer to a source inode already seen.
func (fsys *Writer) copyFromImage(img *image, hardlinks map[hardlinkKey]*fsInode) error {
metaStart := img.metaStartPos()
totalBytes := int64(img.sb.Blocks) << img.sb.BlkSizeBits
if totalBytes <= 0 {
@@ -198,113 +200,129 @@ func (fsys *Writer) copyFromImage(img *image) error {
trailingAddr := inodeAddr + int64(icSize) + int64(xattrSize)
typ := mode & disk.StatTypeMask
// Build fsEntry directly, bypassing builder.Entry + add() overhead.
fe := &fsEntry{
path: cur.path,
mode: mode,
uid: uid,
gid: gid,
mtime: mtime,
mtimeNs: mtimeNs,
size: size,
xattrs: xattrs,
// Build fsInode directly, bypassing builder.Entry + add() overhead.
ino := &fsInode{
mode: mode,
uid: uid,
gid: gid,
mtime: mtime,
mtimeNs: mtimeNs,
size: size,
xattrs: xattrs,
fileClosed: true,
}
if nlink > 0 {
fe.nlink = nlink
fe.nlinkSet = true
ino.nlink = nlink
ino.nlinkSet = true
}
fe.fileClosed = true
if fsys.copyMetadataOnly {
fe.metadataOnly = true
ino.metadataOnly = true
}
switch typ {
case disk.StatTypeDir:
dirSize := int(size)
if dirSize > 0 {
var dirData []byte
switch layout {
case disk.LayoutFlatPlain:
dataAddr := int64(idata) << blkBits
d := at(dataAddr)
if d != nil && len(d) >= dirSize {
dirData = d[:dirSize]
} else {
dirData = make([]byte, dirSize)
if _, err := img.meta.ReadAt(dirData, dataAddr); err != nil {
return fmt.Errorf("read dir data for nid %d: %w", cur.nid, err)
// Detect hard links: if this nid was already registered under an
// earlier directory entry in this CopyFrom call, share that fsInode
// instead of duplicating its data. Directories are excluded — a
// directory's nlink reflects its child-directory count, not a real
// hard link, and BFS only ever visits a given directory nid once.
// nid is only unique within this source image, but hardlinks is
// created fresh for each CopyFrom call, so keying on nid alone
// (dev 0) is safe here.
secondaryHardlink := false
if nlink > 1 && typ != disk.StatTypeDir {
key := hardlinkKey{0, cur.nid}
if prior, ok := hardlinks[key]; ok {
ino = prior
secondaryHardlink = true
} else {
hardlinks[key] = ino
}
}
fe := &fsEntry{path: cur.path, ino: ino}
if !secondaryHardlink {
switch typ {
case disk.StatTypeDir:
dirSize := int(size)
if dirSize > 0 {
var dirData []byte
switch layout {
case disk.LayoutFlatPlain:
dataAddr := int64(idata) << blkBits
d := at(dataAddr)
if d != nil && len(d) >= dirSize {
dirData = d[:dirSize]
} else {
dirData = make([]byte, dirSize)
if _, err := img.meta.ReadAt(dirData, dataAddr); err != nil {
return fmt.Errorf("read dir data for nid %d: %w", cur.nid, err)
}
}
case disk.LayoutFlatInline:
d := at(trailingAddr)
if d != nil && len(d) >= dirSize {
dirData = d[:dirSize]
}
}
case disk.LayoutFlatInline:
d := at(trailingAddr)
if d != nil && len(d) >= dirSize {
dirData = d[:dirSize]
if dirData != nil {
fsys.parseDirBlock(dirData, dirSize, blockSize, cur.path, &queue)
}
}
if dirData != nil {
fsys.parseDirBlock(dirData, dirSize, blockSize, cur.path, &queue)
}
}
case disk.StatTypeSymlink:
if size > 0 {
var linkData []byte
if layout == disk.LayoutFlatPlain {
linkData = make([]byte, size)
if _, err := img.meta.ReadAt(linkData, int64(idata)<<blkBits); err != nil {
return fmt.Errorf("read symlink data for nid %d: %w", cur.nid, err)
case disk.StatTypeSymlink:
if size > 0 {
var linkData []byte
if layout == disk.LayoutFlatPlain {
linkData = make([]byte, size)
if _, err := img.meta.ReadAt(linkData, int64(idata)<<blkBits); err != nil {
return fmt.Errorf("read symlink data for nid %d: %w", cur.nid, err)
}
} else {
linkData = at(trailingAddr)
}
} else {
linkData = at(trailingAddr)
}
if linkData != nil && int(size) <= len(linkData) {
fe.linkTarget = string(linkData[:size])
}
}
case disk.StatTypeReg:
if layout == disk.LayoutChunkBased && size > 0 {
chunkFmt := uint16(idata)
if chunkFmt&disk.LayoutChunkFormatIndexes != 0 {
chunkAddr := trailingAddr
if chunkAddr%8 != 0 {
chunkAddr = (chunkAddr + 7) & ^int64(7)
if linkData != nil && int(size) <= len(linkData) {
ino.linkTarget = string(linkData[:size])
}
fe.chunks = fsys.parseChunks(at(chunkAddr), chunkFmt, size, blkBits, img.deviceIDMask)
fe.contiguous = true
}
case disk.StatTypeReg:
if layout == disk.LayoutChunkBased && size > 0 {
chunkFmt := uint16(idata)
if chunkFmt&disk.LayoutChunkFormatIndexes != 0 {
chunkAddr := trailingAddr
if chunkAddr%8 != 0 {
chunkAddr = (chunkAddr + 7) & ^int64(7)
}
ino.chunks = fsys.parseChunks(at(chunkAddr), chunkFmt, size, blkBits, img.deviceIDMask)
ino.contiguous = true
}
}
case disk.StatTypeChrdev, disk.StatTypeBlkdev:
ino.rdev = disk.RdevFromMode(mode, idata)
}
case disk.StatTypeChrdev, disk.StatTypeBlkdev:
fe.rdev = disk.RdevFromMode(mode, idata)
}
// Remap chunk DeviceIDs for metadata-only sources.
if fsys.copyMetadataOnly && fsys.copyDeviceID > 0 {
offset := fsys.copyDeviceID - 1
for i := range fe.chunks {
fe.chunks[i].DeviceID += offset
// Remap chunk DeviceIDs for metadata-only sources.
if fsys.copyMetadataOnly && fsys.copyDeviceID > 0 {
offset := fsys.copyDeviceID - 1
for i := range ino.chunks {
ino.chunks[i].DeviceID += offset
}
}
}
// Register in the tree.
if cur.path == "/" {
// Update root metadata.
fsys.root.mode = fe.mode
fsys.root.uid = fe.uid
fsys.root.gid = fe.gid
fsys.root.mtime = fe.mtime
fsys.root.mtimeNs = fe.mtimeNs
fsys.root.nlink = fe.nlink
fsys.root.nlinkSet = fe.nlinkSet
fsys.root.xattrs = fe.xattrs
// Update root fsInode.
fsys.root.ino = ino
} else if existing, ok := fsys.byPath[cur.path]; ok {
// Merge overwrites: preserve tree linkage.
savedParent := existing.parent
savedChildren := existing.children
*existing = *fe
existing.parent = savedParent
existing.children = savedChildren
// Merge overwrites: replace fsInode but preserve tree linkage.
// Detach the old fsInode so any remaining hard-linked names get a
// correct nlink.
if existing.ino != ino {
unlinkInode(existing.ino)
}
existing.ino = ino
} else {
fsys.addChild(fe)
}
+2
View File
@@ -23,6 +23,8 @@ func entryFromSys(info fs.FileInfo) *builder.Entry {
MtimeNs: uint32(sys.Mtim.Nsec),
Nlink: uint32(sys.Nlink),
Rdev: uint32(sys.Rdev),
Dev: sys.Dev,
Ino: sys.Ino,
}
default:
return nil
+65 -22
View File
@@ -25,18 +25,19 @@ type onlyWriter struct{ io.Writer }
// erofsWriter serializes EROFS metadata to an io.Writer.
type erofsWriter struct {
entries []*erofsEntry // all entries in NID order
rootNid uint64
metaBlkAddr uint32
totalInodes uint64
buildTime uint64
buildTimeNs uint32
devices []uint64 // per-device block counts (one slot per entry)
blockSize int
chunkBits uint8 // log2(chunkSize / blockSize); chunkSize = blockSize << chunkBits
copyBuf []byte // reusable buffer for io.CopyBuffer
zeroBuf []byte // blockSize-length zero buffer for padding
inodeBuf [disk.SizeInodeExtended]byte // scratch buffer for writeInode
entries []*erofsEntry // all entries in NID order
rootNid uint64
metaBlkAddr uint32
totalInodes uint64
buildTime uint64
buildTimeNs uint32
devices []uint64 // per-device block counts (one slot per entry)
blockSize int
chunkBits uint8 // log2(chunkSize / blockSize); chunkSize = blockSize << chunkBits
noInlineData bool // disable tail-packing for regular files
copyBuf []byte // reusable buffer for io.CopyBuffer
zeroBuf []byte // blockSize-length zero buffer for padding
inodeBuf [disk.SizeInodeExtended]byte // scratch buffer for writeInode
}
// inodeSize returns the on-disk inode header size for e.
@@ -116,11 +117,14 @@ func (w *erofsWriter) writeSeekable(out io.WriteSeeker) error {
func (w *erofsWriter) newMetaBuffer() *bytes.Buffer {
totalMetaBytes := 0
for _, e := range w.entries {
if e.hardLinkPrimary != nil {
continue // secondary entries share the primary's on-disk inode
}
isz := disk.SizeInodeExtended
if e.compact {
isz = disk.SizeInodeCompact
}
sz := isz + e.xattrSize + e.trailingSize
sz := isz + e.xattrSize + e.chunkPad + e.trailingSize
if sz%32 != 0 {
sz = (sz + 31) & ^31
}
@@ -141,8 +145,11 @@ func (w *erofsWriter) assignDataBlocks() {
// Metadata-first: data blocks come after metadata.
totalMetaBytes := 0
for _, e := range w.entries {
if e.hardLinkPrimary != nil {
continue
}
expectedOff := int(e.nid) * 32
sz := inodeCoreSize(e) + e.xattrSize + e.trailingSize
sz := inodeCoreSize(e) + e.xattrSize + e.chunkPad + e.trailingSize
if sz%32 != 0 {
sz = (sz + 31) & ^31
}
@@ -154,6 +161,9 @@ func (w *erofsWriter) assignDataBlocks() {
metaBlocks := (totalMetaBytes + w.blockSize - 1) / w.blockSize
addr := uint32(w.sbAreaBlocks() + metaBlocks)
for _, e := range w.entries {
if e.hardLinkPrimary != nil {
continue
}
if ds := w.flatPlainDataSize(e); ds > 0 {
e.dataBlkAddr = addr
addr += uint32((ds + w.blockSize - 1) / w.blockSize)
@@ -163,6 +173,9 @@ func (w *erofsWriter) assignDataBlocks() {
// Data-first: data starts after superblock area.
addr := uint32(w.sbAreaBlocks())
for _, e := range w.entries {
if e.hardLinkPrimary != nil {
continue // secondary entries share the primary's data blocks
}
if ds := w.flatPlainDataSize(e); ds > 0 {
e.dataBlkAddr = addr
addr += uint32((ds + w.blockSize - 1) / w.blockSize)
@@ -194,11 +207,14 @@ func (w *erofsWriter) sbAreaBlocks() int {
func (w *erofsWriter) metadataBytes() int {
curOff := 0
for _, e := range w.entries {
if e.hardLinkPrimary != nil {
continue // secondary entries have no on-disk inode
}
expectedOff := int(e.nid) * 32
if curOff < expectedOff {
curOff = expectedOff
}
sz := inodeCoreSize(e) + e.xattrSize + e.trailingSize
sz := inodeCoreSize(e) + e.xattrSize + e.chunkPad + e.trailingSize
if rem := sz % 32; rem != 0 {
sz += 32 - rem
}
@@ -213,9 +229,12 @@ func (w *erofsWriter) writeBlock0(buf io.Writer) error {
totalMetaBytes := w.metadataBytes()
metaBlocks := (totalMetaBytes + w.blockSize - 1) / w.blockSize
// Count data blocks.
// Count data blocks (skip secondary hard-link entries).
dataBlocks := 0
for _, e := range w.entries {
if e.hardLinkPrimary != nil {
continue
}
if ds := w.flatPlainDataSize(e); ds > 0 {
dataBlocks += (ds + w.blockSize - 1) / w.blockSize
}
@@ -283,6 +302,11 @@ func (w *erofsWriter) writeBlock0(buf io.Writer) error {
func (w *erofsWriter) writeMetadataInodes(buf io.Writer) error {
metaStart := 0
for _, e := range w.entries {
// Secondary hard-link entries share the primary's on-disk inode.
if e.hardLinkPrimary != nil {
continue
}
expectedOff := int(e.nid) * 32
if expectedOff > metaStart {
if _, err := buf.Write(w.zeroBuf[:expectedOff-metaStart]); err != nil {
@@ -312,18 +336,31 @@ func (w *erofsWriter) writeMetadataInodes(buf io.Writer) error {
switch e.mode & disk.StatTypeMask {
case disk.StatTypeReg:
if e.layout == disk.LayoutChunkBased && (e.size > 0 || len(e.chunks) > 0) {
// Align the chunk-index map to the chunk-index unit.
if e.chunkPad > 0 {
if _, err := buf.Write(w.zeroBuf[:e.chunkPad]); err != nil {
return err
}
metaStart += e.chunkPad
}
if err := w.writeChunkIndexes(buf, e); err != nil {
return fmt.Errorf("write chunks for %s: %w", e.path, err)
}
metaStart += e.trailingSize
} else if e.layout == disk.LayoutFlatInline && e.size > 0 && e.data != nil {
n, err := io.CopyBuffer(onlyWriter{buf}, io.LimitReader(e.data, int64(e.size)), w.copyBuf)
// e.data may be an unbounded reader (e.g. directData from CopyFrom);
// limit to e.size bytes to prevent overwriting subsequent metadata.
expected := int64(e.size)
n, err := io.CopyBuffer(onlyWriter{buf}, io.LimitReader(e.data, expected), w.copyBuf)
if c, ok := e.data.(io.Closer); ok {
_ = c.Close()
}
if err != nil {
return fmt.Errorf("write inline data for %s: %w", e.path, err)
}
if n != expected {
return fmt.Errorf("write inline data for %s: short read: got %d bytes, expected %d", e.path, n, expected)
}
metaStart += int(n)
}
case disk.StatTypeDir:
@@ -348,7 +385,7 @@ func (w *erofsWriter) writeMetadataInodes(buf io.Writer) error {
if e.compact {
inodeSize = disk.SizeInodeCompact
}
totalWritten := inodeSize + e.xattrSize + e.trailingSize
totalWritten := inodeSize + e.xattrSize + e.chunkPad + e.trailingSize
if totalWritten%32 != 0 {
padSize := 32 - (totalWritten % 32)
if _, err := buf.Write(w.zeroBuf[:padSize]); err != nil {
@@ -530,11 +567,14 @@ func (w *erofsWriter) writeDirents(buf io.Writer, e *erofsEntry) (int, error) {
}
// Build the full entry list including "." and ".." then sort
// alphabetically. EROFS requires dirents to be sorted within
// each block; "." and ".." are not guaranteed to be first.
// alphabetically. EROFS requires dirents to be sorted within each block;
// "." and ".." are not guaranteed to sort first (filenames with ASCII
// values below '.' such as '-' or ',' sort before them).
allEnts := make([]direntInfo, 0, len(e.children)+2)
allEnts = append(allEnts, direntInfo{".", e.nid, disk.FileTypeDir})
allEnts = append(allEnts, direntInfo{"..", e.parentNid, disk.FileTypeDir})
allEnts = append(allEnts,
direntInfo{".", e.nid, disk.FileTypeDir},
direntInfo{"..", e.parentNid, disk.FileTypeDir},
)
for _, c := range e.children {
allEnts = append(allEnts, direntInfo{
name: c.name,
@@ -614,6 +654,9 @@ func (w *erofsWriter) writeDirents(buf io.Writer, e *erofsEntry) (int, error) {
// writeDataBlocks writes data blocks for flat-plain entries directly to out.
func (w *erofsWriter) writeDataBlocks(out io.Writer) error {
for _, e := range w.entries {
if e.hardLinkPrimary != nil {
continue // secondary hard-link entries share the primary's data blocks
}
ds := w.flatPlainDataSize(e)
if ds == 0 {
continue
+2 -2
View File
@@ -23,9 +23,9 @@ func (idx xattrIndex) String() string {
case 1:
return "user."
case 2:
return "system.posix_acl_access."
return "system.posix_acl_access"
case 3:
return "system.posix_acl_default."
return "system.posix_acl_default"
case 4:
return "trusted."
case 5:
+1 -1
View File
@@ -345,7 +345,7 @@ github.com/docker/go-units
## explicit; go 1.13
github.com/emicklei/go-restful/v3
github.com/emicklei/go-restful/v3/log
# github.com/erofs/go-erofs v0.3.1
# github.com/erofs/go-erofs v0.3.2
## explicit; go 1.23
github.com/erofs/go-erofs
github.com/erofs/go-erofs/internal/builder