github.com/google/osv-scalibr@v0.4.1/fs/diriterate/diriterate.go (about) 1 // Copyright 2025 Google LLC 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package diriterate provides a utility for iterating over the contents of a directory without 16 // loading all of it into memory at once. 17 package diriterate 18 19 import ( 20 "errors" 21 "io" 22 "io/fs" 23 24 scalibrfs "github.com/google/osv-scalibr/fs" 25 ) 26 27 // ReadDir reads the named directory and returns an iterator over the directory entries. 28 func ReadDir(fsys scalibrfs.FS, name string) (*DirIterator, error) { 29 // Check if the path is accessible 30 _, err := fsys.Stat(name) 31 if err != nil { 32 return nil, &fs.PathError{Op: "stat", Path: name, Err: err} 33 } 34 35 // Try to open the directory 36 file, err := fsys.Open(name) 37 if err != nil { 38 // The underlying filesystem might not have implemented Open() for directories. 39 // In this case, we fall back to reading all entries using readDirAll() 40 return readDirAll(fsys, name) 41 } 42 43 // Check if the file supports incremental Readdir 44 dir, ok := file.(fs.ReadDirFile) 45 if !ok { 46 // If ReadDirFile is not implemented, close the file and fall back to reading all entries 47 // (Uses more memory since it reads all subdirs at once.) 48 if err := file.Close(); err != nil { 49 return nil, &fs.PathError{Op: "close", Path: name, Err: err} 50 } 51 return readDirAll(fsys, name) 52 } 53 54 return &DirIterator{dir: dir}, nil 55 } 56 57 // readDirAll reads all directory entries using fsys.ReadDir 58 // and returns a DirIterator with preloaded entries. 59 func readDirAll(fsys scalibrfs.FS, name string) (*DirIterator, error) { 60 files, err := fsys.ReadDir(name) 61 if err != nil { 62 return nil, &fs.PathError{Op: "readdir", Path: name, Err: errors.New("not implemented")} 63 } 64 return &DirIterator{files: files, curr: 0}, nil 65 } 66 67 // DirIterator iterates over the contents of a directory without loading all 68 // of it into memory at once. 69 type DirIterator struct { 70 // dir is used to iterate directory entries 71 dir fs.ReadDirFile 72 // if dir doesn't implement fs.ReadDirFile, file and curr are used as 73 // fallback to iterate through a preloaded list of files 74 files []fs.DirEntry 75 curr int 76 } 77 78 // Next returns the next fs.DirEntry from the directory. If error is nil, there will be a 79 // fs.DirEntry returned. 80 func (i *DirIterator) Next() (fs.DirEntry, error) { 81 if len(i.files) > 0 { 82 if i.curr >= len(i.files) { 83 return nil, io.EOF 84 } 85 i.curr++ 86 return i.files[i.curr-1], nil 87 } 88 89 if i.dir == nil { 90 // This is an iterator for an empty directory, so we return EOF immediately. 91 return nil, io.EOF 92 } 93 94 list, err := i.dir.ReadDir(1) 95 if err != nil { 96 return nil, err 97 } 98 99 return list[0], nil 100 } 101 102 // Close closes the directory file. 103 func (i *DirIterator) Close() error { 104 if i.dir == nil { 105 return nil 106 } 107 return i.dir.Close() 108 }