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  }