github.com/Schaudge/grailbase@v0.0.0-20240223061707-44c758a471c0/file/loopbackfs/loopbackfs.go (about) 1 package loopbackfs 2 3 import ( 4 "context" 5 "io/ioutil" 6 "os" 7 "path" 8 "time" 9 10 "github.com/Schaudge/grailbase/file/fsnode" 11 "github.com/Schaudge/grailbase/ioctx/fsctx" 12 "github.com/Schaudge/grailbase/ioctx/spliceio" 13 ) 14 15 // New returns an fsnode.T representing a path on the local filesystem. 16 // TODO: Replace this with a generic io/fs.FS wrapper around os.DirFS, after upgrading Go. 17 func New(name string, path string) (fsnode.T, error) { 18 info, err := os.Stat(path) 19 if err != nil { 20 return nil, err 21 } 22 node := newT(name, info, path) 23 if node == nil { 24 return nil, os.ErrInvalid 25 } 26 return node, nil 27 } 28 29 func newT(name string, info os.FileInfo, path string) fsnode.T { 30 switch info.Mode() & os.ModeType { 31 case os.ModeDir: 32 // Temporary hack: record the original path so we can peek at it. 33 // TODO: Eventually, adapt our libraries to operate on FS's directly. 34 // TODO: Consider preserving executable bits. But, copying permissions without also checking 35 // owner UID/GID may not make sense. 36 info := fsnode.NewDirInfo(name).WithModTime(info.ModTime()).WithSys(path). 37 WithCacheableFor(time.Hour) 38 return parent{dir: path, FileInfo: info} 39 case 0: 40 info := fsnode.NewRegInfo(name).WithModTime(info.ModTime()).WithSys(path). 41 WithCacheableFor(time.Hour) 42 return leaf{path, info} 43 } 44 return nil 45 } 46 47 type parent struct { 48 fsnode.ParentReadOnly 49 dir string 50 fsnode.FileInfo 51 } 52 53 var _ fsnode.Parent = parent{} 54 55 func (p parent) FSNodeT() {} 56 57 func (p parent) Child(_ context.Context, name string) (fsnode.T, error) { 58 return New(name, path.Join(p.dir, name)) 59 } 60 61 type iterator struct { 62 dir string 63 fetched bool 64 delegate fsnode.Iterator 65 } 66 67 func (p parent) Children() fsnode.Iterator { return &iterator{dir: p.dir} } 68 69 func (it *iterator) ensureFetched() error { 70 if it.fetched { 71 if it.delegate == nil { 72 return os.ErrClosed 73 } 74 return nil 75 } 76 entries, err := ioutil.ReadDir(it.dir) 77 if err != nil { 78 return err 79 } 80 nodes := make([]fsnode.T, 0, len(entries)) 81 for _, info := range entries { 82 fullPath := path.Join(it.dir, info.Name()) 83 node := newT(info.Name(), info, fullPath) 84 if node == nil { 85 continue 86 } 87 nodes = append(nodes, node) 88 } 89 it.fetched = true 90 it.delegate = fsnode.NewIterator(nodes...) 91 return nil 92 } 93 94 func (it *iterator) Next(ctx context.Context) (fsnode.T, error) { 95 if err := it.ensureFetched(); err != nil { 96 return nil, err 97 } 98 return it.delegate.Next(ctx) 99 } 100 101 func (it *iterator) Close(context.Context) error { 102 it.fetched = true 103 it.delegate = nil 104 return nil 105 } 106 107 type leaf struct { 108 path string 109 fsnode.FileInfo 110 } 111 112 var _ fsnode.Leaf = leaf{} 113 114 func (l leaf) FSNodeT() {} 115 116 func (l leaf) OpenFile(context.Context, int) (fsctx.File, error) { 117 file, err := os.Open(l.path) 118 if err != nil { 119 return nil, err 120 } 121 return (*spliceio.OSFile)(file), nil 122 }