Files

90 lines
2.3 KiB
Go

package scoutfs
import (
"errors"
"fmt"
"unsafe"
)
const allocInosBatchSize = 8192
// FetchAllocatedInos retrieves allocated inode numbers starting from startIno.
// It returns the inode numbers found in the allocation group. An empty slice
// signals that no more allocated inodes exist beyond startIno.
func (c *Client) FetchAllocatedInos(startIno uint64) ([]uint64, error) {
buf := make([]uint64, allocInosBatchSize)
req := GetAllocatedInosRequest{
StartIno: startIno,
InosPtr: uint64(uintptr(unsafe.Pointer(&buf[0]))),
InosBytes: uint64(len(buf) * 8),
}
if err := ioctlRaw(c.fd(), iocGetAllocatedInos, unsafe.Pointer(&req)); err != nil {
// Kernel signals end-of-iteration via ENOENT.
if errors.Is(err, ErrInodeNotFound) {
return nil, nil
}
return nil, fmt.Errorf("get allocated inos ioctl from ino %d: %w", startIno, err)
}
// The kernel writes the count of returned inodes into InosBytes.
count := req.InosBytes / 8
if count > uint64(len(buf)) {
count = uint64(len(buf))
}
result := make([]uint64, count)
copy(result, buf[:count])
return result, nil
}
// inoGroupSize is the ScoutFS inode allocation group size (SCOUTFS_LOCK_INODE_GROUP_NR).
const inoGroupSize = 1024
// maxEmptyGroups is the number of consecutive empty inode groups before we
// assume there are no more allocated inodes.
const maxEmptyGroups = 256
// AllAllocatedInos iterates through all allocated inodes in the filesystem,
// calling fn for each batch. Iteration stops when fn returns false or all
// inodes have been enumerated. Begins at FirstUserIno since inodes below that
// are reserved by ScoutFS.
func (c *Client) AllAllocatedInos(fn func(inos []uint64) bool) error {
startIno := uint64(FirstUserIno)
emptyRuns := 0
for {
inos, err := c.FetchAllocatedInos(startIno)
if err != nil {
return fmt.Errorf("iterating allocated inos from %d: %w", startIno, err)
}
if len(inos) == 0 {
emptyRuns++
if emptyRuns >= maxEmptyGroups {
return nil
}
// Advance to the next inode allocation group.
nextGroup := (startIno/inoGroupSize + 1) * inoGroupSize
if nextGroup <= startIno {
return nil
}
startIno = nextGroup
continue
}
emptyRuns = 0
if !fn(inos) {
return nil
}
nextStart := inos[len(inos)-1] + 1
if nextStart <= startIno {
return nil
}
startIno = nextStart
}
}