func (it *ReadDirIterator) FileInfoAndLen() (*FileInfo, int64) {
	return it.finfo, it.finfoLen
}
FileInfoAndLen returns the FileInfo consumed by calling Next()
along with the number of bytes consumed in the directory.
package iso9660
import (
	"fmt"
	"io"
	"os"
	"path/filepath"
	"strings"
	"syscall"
	"time"
	"github.com/badgerodon/collections/queue"
	lru "github.com/hashicorp/golang-lru"
	"github.com/NVIDIA/vdisc/pkg/iso9660/rrip"
	"github.com/NVIDIA/vdisc/pkg/iso9660/susp"
	"github.com/NVIDIA/vdisc/pkg/safecast"
)
type Walker struct {
	iso        io.ReaderAt
	lstatCache *lru.ARCCache
}
func NewWalker(iso io.ReaderAt) *Walker {
	lstatCache, err := lru.NewARC(256 * 1024)
	if err != nil {
		panic(err)
	}
	return &Walker{
		iso:        iso,
		lstatCache: lstatCache,
	}
}
func (w *Walker) Walk(root string, walkFn filepath.WalkFunc) (err error) {
	rootParts := w.pathParts(root)
	rootFi, rootErr := w.lstat(rootParts)
	if err = walkFn(root, rootFi, rootErr); err != nil {
		return
	}
	q := queue.New()
	if rootFi != nil && rootFi.IsDir() {
		q.Enqueue(&walkItem{
			parts: rootParts,
			fi:    rootFi,
		})
	}
	for q.Len() > 0 {
		item := q.Dequeue().(*walkItem)
		err = iterDir(w.iso, item.fi.Extent(), item.fi.Size(), func(fi *FileInfo) bool {
			path := "/" + strings.Join(item.parts, "/")
			if err = walkFn(path, fi, nil); err != nil {
				return false
			}
			if fi.IsDir() && fi.Name() != "." && fi.Name() != ".." {
				q.Enqueue(&walkItem{
					parts: append(item.parts, fi.Name()),
					fi:    fi,
				})
			}
			return true
		})
		if err != nil {
			return
		}
	}
	return
}
func (w *Walker) Stat(path string) (*FileInfo, error) {
	parts := w.pathParts(path)
	return w.stat(parts)
}
func (w *Walker) Lstat(path string) (*FileInfo, error) {
	parts := w.pathParts(path)
	return w.lstat(parts)
}
func (w *Walker) Open(name string) (*File, error) {
	parts := w.pathParts(name)
	fi, err := w.stat(parts)  
	if err != nil {
		return nil, fmt.Errorf("open %s: %+v", name, err)
	}
	return &File{
		name: name,
		fi:   fi,
		r:    io.NewSectionReader(w.iso, int64(fi.Extent())*LogicalBlockSize, fi.Size()),
	}, nil
}
func (w *Walker) ReadDir(dirname string) ([]*FileInfo, error) {
	var entries []*FileInfo
	fi, err := w.Lstat(dirname)
	if err != nil {
		return nil, err
	}
	if !fi.IsDir() {
		return nil, syscall.ENOTDIR
	}
	err = iterDir(w.iso, fi.Extent(), fi.Size(), func(fi *FileInfo) bool {
		cacheKey := filepath.Join(filepath.Clean("/"+dirname), fi.Name())
		w.lstatCache.Add(cacheKey, fi)
		entries = append(entries, fi)
		return true
	})
	if err != nil {
		return nil, err
	}
	return entries, nil
}
const SymlinkRecursionLimit = 40
func (w *Walker) stat(parts []string) (*FileInfo, error) {
	for i := 0; i < SymlinkRecursionLimit; i++ {
		fi, err := w.lstat(parts)
		if err != nil {
			return nil, err
		}
		if fi.Mode()&os.ModeSymlink != 0 {
			path := "/" + strings.Join(parts, "/")
			if !fi.IsDir() {
				path = filepath.Dir(path)
			}
			joined := filepath.Clean(filepath.Join(path, fi.Target()))
			parts = w.pathParts(joined)
		} else {
			return fi, nil
		}
	}
	return nil, syscall.ELOOP
}
func (w *Walker) lstat(parts []string) (*FileInfo, error) {
	var start LogicalBlockAddress
	var size int64
	var cached *FileInfo
	cached, parts = w.lstatCacheSearch(append([]string{"."}, parts...))
	if cached == nil {
		var pvd PrimaryVolumeDescriptor
		pvdSector := io.NewSectionReader(w.iso, 16*LogicalBlockSize, LogicalBlockSize)
		if err := DecodePrimaryVolumeDescriptor(pvdSector, &pvd); err != nil {
			return nil, err
		}
		start = pvd.RootStart
		size = int64(pvd.RootLength)
	} else if len(parts) > 0 {
		start = cached.Extent()
		size = cached.Size()
	} else {
		return cached, nil
	}
	var path []string
	for len(parts) > 0 {
		part := parts[0]
		parts = parts[1:]
		var partInfo *FileInfo
		err := iterDir(w.iso, start, size, func(fi *FileInfo) bool {
			if fi.IsDir() {
				w.lstatCacheAdd(append(path, fi.Name()), fi)
			}
			if fi.Name() == part {
				partInfo = fi
				return false
			}
			return true
		})
		if err != nil {
			return nil, err
		}
		if partInfo == nil {
			return nil, syscall.ENOENT
		}
		if len(parts) == 0 {
			return partInfo, nil
		}
		if !partInfo.IsDir() {
			return nil, syscall.ENOTDIR
		}
		path = append(path, part)
		start = partInfo.Extent()
		size = partInfo.Size()
	}
	panic("never")
}
func (w *Walker) lstatCacheAdd(parts []string, fi *FileInfo) {
	key := filepath.Clean("/" + strings.Join(parts, "/"))
	w.lstatCache.Add(key, fi)
}
func (w *Walker) lstatCacheSearch(parts []string) (*FileInfo, []string) {
	for i := len(parts); i >= 0; i-- {
		key := filepath.Clean("/" + strings.Join(parts[:i], "/"))
		if fi, ok := w.lstatCache.Get(key); ok {
			return fi.(*FileInfo), parts[i:]
		}
	}
	return nil, parts
}
func iterDir(iso io.ReaderAt, start LogicalBlockAddress, size int64, visit func(fi *FileInfo) bool) error {
	it := NewReadDirIterator(iso, start, size, 0)
	for it.Next() {
		fi, _ := it.FileInfoAndLen()
		if cont := visit(fi); !cont {
			break
		}
	}
	return it.Err()
}
func (w *Walker) pathParts(path string) []string {
	var parts []string
	cleaned := filepath.Clean("/" + path)
	if len(cleaned) > 1 {
		parts = strings.Split(cleaned[1:], "/")
	}
	return parts
}
type walkItem struct {
	parts []string
	fi    *FileInfo
}
type aggregateRec struct {
	rec    DirectoryRecord
	length int64
}
func allSystemUseEntries(iso io.ReaderAt, rec *DirectoryRecord) ([]susp.SystemUseEntry, error) {
	unconsumed := rec.SystemUse
	var systemUse []susp.SystemUseEntry
	for len(unconsumed) > 0 {
		entry := unconsumed[0]
		unconsumed = unconsumed[1:]
		switch v := entry.(type) {
		case *susp.ContinuationAreaEntry:
			ceStart := v.ContinuationStart()*LogicalBlockSize + v.ContinuationOffset()
			ceLen := v.ContinuationLength()
			ce := io.NewSectionReader(iso, int64(ceStart), int64(ceLen))
			more, err := DecodeSystemUseEntries(ce)
			if err != nil {
				return nil, err
			}
			unconsumed = append(unconsumed, more...)
		default:
			systemUse = append(systemUse, entry)
		}
	}
	return systemUse, nil
}
func NewReadDirIterator(iso io.ReaderAt, start LogicalBlockAddress, size int64, off int64) *ReadDirIterator {
	recIt := NewDirectoryRecordIterator(iso, start, size, off)
	return &ReadDirIterator{
		iso:       iso,
		recIt:     recIt,
		finfoLen:  -1,
		peekedLen: -1,
	}
}
type ReadDirIterator struct {
	iso       io.ReaderAt
	recIt     *DirectoryRecordIterator
	finfo     *FileInfo
	finfoLen  int64
	peeked    *DirectoryRecord
	peekedLen int64
	err       error
	exhausted bool
}
func (it *ReadDirIterator) Err() error {
	return it.err
}
func (it *ReadDirIterator) Next() bool {
	if it.exhausted || it.err != nil {
		return false
	}
	var curr *DirectoryRecord
	var currLen int64
	if it.peeked == nil {
		if it.recIt.Next() {
			curr, currLen = it.recIt.RecordAndLen()
		} else {
			it.exhausted = true
			it.err = it.recIt.Err()
			return false
		}
	} else {
		curr = it.peeked
		currLen = it.peekedLen
		it.peeked = nil
		it.peekedLen = -1
	}
	size := int64(curr.Length)
	for {
		if it.recIt.Next() {
			var next *DirectoryRecord
			var nextLen int64
			next, nextLen = it.recIt.RecordAndLen()
			if next.Identifier == curr.Identifier {
				currLen += nextLen
				size += int64(next.Length)
			} else {
				it.peeked = next
				it.peekedLen = nextLen
				break
			}
		} else {
			it.exhausted = true
			it.err = it.recIt.Err()
			break
		}
	}
	systemUse, err := allSystemUseEntries(it.iso, curr)
	if err != nil {
		it.err = err
		return false
	}
	name, hasName := rrip.DecodeName(systemUse)
	if !hasName {
		if curr.Identifier == string([]byte{0x0}) {
			name = "."
		} else if curr.Identifier == string([]byte{0x1}) {
			name = ".."
		} else {
			name = curr.Identifier
		}
	}
	isDir := curr.Flags&FileFlagDir != 0
	target, isSymlink := rrip.DecodeSymlink(systemUse)
	if isSymlink {
		size = int64(len(target))
	}
	var mode os.FileMode
	nlink := uint32(1)
	var uid uint32
	var gid uint32
	ino := uint32(curr.Start)
	if pe, ok := rrip.DecodePosixEntry(systemUse); ok {
		mode = pe.Mode & os.ModePerm
		if pe.Mode&syscall.S_IFDIR == syscall.S_IFDIR {
			mode |= os.ModeDir
		} else if pe.Mode&syscall.S_IFLNK == syscall.S_IFLNK {
			mode |= os.ModeSymlink
		}
		nlink = pe.Nlink
		uid = pe.Uid
		gid = pe.Gid
		ino = pe.Ino
	} else if isDir {
		mode = os.ModeDir | 0555
	} else if isSymlink {
		mode = os.ModeSymlink | 0777
	} else {
		mode = 0444
	}
	modTime := time.Unix(0, 0).UTC()
	if ts, ok := rrip.DecodeTimestamps(systemUse); ok {
		modTime = ts.Modified.UTC()
	}
	it.finfo = &FileInfo{
		name:    name,
		size:    size,
		mode:    mode,
		nlink:   nlink,
		uid:     uid,
		gid:     gid,
		ino:     ino,
		modTime: modTime,
		isDir:   isDir,
		extent:  curr.Start,
		target:  target,
	}
	it.finfoLen = currLen
	return true
}