github.com/google/osv-scalibr@v0.4.1/extractor/filesystem/internal/walkdir_iterate.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 internal 16 17 // Derived from Go's src/io/fs/walk.go 18 // 19 // Copyright 2020 The Go Authors. All rights reserved. 20 // Use of this source code is governed by a BSD-style 21 // license that can be found in the LICENSE file. 22 23 import ( 24 "errors" 25 "io" 26 "io/fs" 27 "path" 28 29 scalibrfs "github.com/google/osv-scalibr/fs" 30 "github.com/google/osv-scalibr/fs/diriterate" 31 ) 32 33 type postWalkDirFunc func(path string, d fs.DirEntry) 34 35 // walkDirUnsorted recursively descends path, calling walkDirFn. From caller side this function 36 // should be equivalent to fs.walkDir, except that the files are not ordered, but walked in the 37 // order returned by the file system. This function does not store the full directory in memory, 38 // which should reduce the memory footprint from O(n) to O(1). 39 // This might lead to inconsistencies in the seen files, as another process might delete a file 40 // during the walk, which is then not seen by the walk. That problem existed also with fs.WalkDir, 41 // which would return files which do not exist anymore. More details for unix: 42 // https://man7.org/linux/man-pages/man2/getdents.2.html 43 func walkDirUnsorted(fsys scalibrfs.FS, name string, d fs.DirEntry, walkDirFn fs.WalkDirFunc, postFN postWalkDirFunc) error { 44 if postFN != nil { 45 defer postFN(name, d) 46 } 47 // This is the main call to walkDirFn for files and directories, without errors. 48 if err := walkDirFn(name, d, nil); err != nil || !d.IsDir() { 49 if errors.Is(err, fs.SkipDir) && d.IsDir() { 50 // Successfully skipped directory. 51 err = nil 52 } 53 return err 54 } 55 56 dirs, err := diriterate.ReadDir(fsys, name) 57 if err != nil { 58 // Second call, to report ReadDir error. 59 // Same error handling as in fs.WalkDir: If an error occurred, the walkDirFn is called again, 60 // which can decide to continue (nil), SkipDir or skip all by other errors (e.g. SkipAll). 61 err = walkDirFn(name, d, err) 62 if err != nil { 63 if errors.Is(err, fs.SkipDir) && d.IsDir() { 64 err = nil 65 } 66 return err 67 } 68 // End iteration after an error 69 return nil 70 } 71 // Error can be ignored, as no write is happening. 72 defer dirs.Close() 73 74 for { 75 d1, err := dirs.Next() 76 if err != nil { 77 if errors.Is(err, io.EOF) { 78 break 79 } 80 // Second call, to report ReadDir error. 81 // Same error handling as in fs.WalkDir: If an error occurred, the walkDirFn is called again, 82 // which can decide to continue (nil), SkipDir or skip all by other errors (e.g. SkipAll). 83 err = walkDirFn(name, d, err) 84 if err != nil { 85 if errors.Is(err, fs.SkipDir) && d.IsDir() { 86 err = nil 87 } 88 return err 89 } 90 // End iteration after an error 91 return nil 92 } 93 name1 := path.Join(name, d1.Name()) 94 if err := walkDirUnsorted(fsys, name1, d1, walkDirFn, postFN); err != nil { 95 if errors.Is(err, fs.SkipDir) { 96 break 97 } 98 return err 99 } 100 } 101 return nil 102 } 103 104 // WalkDirUnsorted walks the file tree rooted at root, calling fn for each file or 105 // directory in the tree, including root. It also calls postFN after the walk of the 106 // file or dir has finished, including the traversal of all sub-directories. 107 // 108 // All errors that arise visiting files and directories are filtered by fn: 109 // see the [fs.WalkDirFunc] documentation for details. 110 // 111 // WalkDirUnsorted does not follow symbolic links found in directories, 112 // but if root itself is a symbolic link, its target will be walked. 113 func WalkDirUnsorted(fsys scalibrfs.FS, root string, fn fs.WalkDirFunc, postFN postWalkDirFunc) error { 114 info, err := fs.Stat(fsys, root) 115 if err != nil { 116 err = fn(root, nil, err) 117 } else { 118 err = walkDirUnsorted(fsys, root, fs.FileInfoToDirEntry(info), fn, postFN) 119 } 120 if errors.Is(err, fs.SkipDir) || errors.Is(err, fs.SkipAll) { 121 return nil 122 } 123 return err 124 }