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 } }