github.com/IBM/fsgo@v0.0.0-20220920202152-e16fd2119d49/iofs.go (about) 1 // Copyright 2022 IBM Inc. All rights reserved 2 // Copyright © 2014 Steve Francia <spf@spf13.com> 3 // 4 // SPDX-License-Identifier: Apache2.0 5 package fsgo 6 7 import ( 8 "io" 9 "io/fs" 10 "os" 11 "path" 12 "sort" 13 "time" 14 15 "github.com/IBM/fsgo/internal/common" 16 ) 17 18 // IOFS adopts fsgo.Fs to stdlib io/fs.FS 19 type IOFS struct { 20 Fs 21 } 22 23 func NewIOFS(fs Fs) IOFS { 24 return IOFS{Fs: fs} 25 } 26 27 var ( 28 _ fs.FS = IOFS{} 29 _ fs.GlobFS = IOFS{} 30 _ fs.ReadDirFS = IOFS{} 31 _ fs.ReadFileFS = IOFS{} 32 _ fs.StatFS = IOFS{} 33 _ fs.SubFS = IOFS{} 34 ) 35 36 func (iofs IOFS) Open(name string) (fs.File, error) { 37 const op = "open" 38 39 // by convention for fs.FS implementations we should perform this check 40 if !fs.ValidPath(name) { 41 return nil, iofs.wrapError(op, name, fs.ErrInvalid) 42 } 43 44 file, err := iofs.Fs.Open(name) 45 if err != nil { 46 return nil, iofs.wrapError(op, name, err) 47 } 48 49 // file should implement fs.ReadDirFile 50 if _, ok := file.(fs.ReadDirFile); !ok { 51 file = readDirFile{file} 52 } 53 54 return file, nil 55 } 56 57 func (iofs IOFS) Glob(pattern string) ([]string, error) { 58 const op = "glob" 59 60 // fsgo.Glob does not perform this check but it's required for implementations 61 if _, err := path.Match(pattern, ""); err != nil { 62 return nil, iofs.wrapError(op, pattern, err) 63 } 64 65 items, err := Glob(iofs.Fs, pattern) 66 if err != nil { 67 return nil, iofs.wrapError(op, pattern, err) 68 } 69 70 return items, nil 71 } 72 73 func (iofs IOFS) ReadDir(name string) ([]fs.DirEntry, error) { 74 f, err := iofs.Fs.Open(name) 75 if err != nil { 76 return nil, iofs.wrapError("readdir", name, err) 77 } 78 79 defer f.Close() 80 81 if rdf, ok := f.(fs.ReadDirFile); ok { 82 items, err := rdf.ReadDir(-1) 83 if err != nil { 84 return nil, iofs.wrapError("readdir", name, err) 85 } 86 sort.Slice(items, func(i, j int) bool { return items[i].Name() < items[j].Name() }) 87 return items, nil 88 } 89 90 items, err := f.Readdir(-1) 91 if err != nil { 92 return nil, iofs.wrapError("readdir", name, err) 93 } 94 sort.Sort(byName(items)) 95 96 ret := make([]fs.DirEntry, len(items)) 97 for i := range items { 98 ret[i] = common.FileInfoDirEntry{FileInfo: items[i]} 99 } 100 101 return ret, nil 102 } 103 104 func (iofs IOFS) ReadFile(name string) ([]byte, error) { 105 const op = "readfile" 106 107 if !fs.ValidPath(name) { 108 return nil, iofs.wrapError(op, name, fs.ErrInvalid) 109 } 110 111 bytes, err := ReadFile(iofs.Fs, name) 112 if err != nil { 113 return nil, iofs.wrapError(op, name, err) 114 } 115 116 return bytes, nil 117 } 118 119 func (iofs IOFS) Sub(dir string) (fs.FS, error) { return IOFS{NewBasePathFs(iofs.Fs, dir)}, nil } 120 121 func (IOFS) wrapError(op, path string, err error) error { 122 if _, ok := err.(*fs.PathError); ok { 123 return err // don't need to wrap again 124 } 125 126 return &fs.PathError{ 127 Op: op, 128 Path: path, 129 Err: err, 130 } 131 } 132 133 // readDirFile provides adapter from fsgo.File to fs.ReadDirFile needed for correct Open 134 type readDirFile struct { 135 File 136 } 137 138 var _ fs.ReadDirFile = readDirFile{} 139 140 func (r readDirFile) ReadDir(n int) ([]fs.DirEntry, error) { 141 items, err := r.File.Readdir(n) 142 if err != nil { 143 return nil, err 144 } 145 146 ret := make([]fs.DirEntry, len(items)) 147 for i := range items { 148 ret[i] = common.FileInfoDirEntry{FileInfo: items[i]} 149 } 150 151 return ret, nil 152 } 153 154 // FromIOFS adopts io/fs.FS to use it as fsgo.Fs 155 // Note that io/fs.FS is read-only so all mutating methods will return fs.PathError with fs.ErrPermission 156 // To store modifications you may use fsgo.CopyOnWriteFs 157 type FromIOFS struct { 158 fs.FS 159 } 160 161 var _ Fs = FromIOFS{} 162 163 func (f FromIOFS) Create(name string) (File, error) { return nil, notImplemented("create", name) } 164 165 func (f FromIOFS) Mkdir(name string, perm os.FileMode) error { return notImplemented("mkdir", name) } 166 167 func (f FromIOFS) MkdirAll(path string, perm os.FileMode) error { 168 return notImplemented("mkdirall", path) 169 } 170 171 func (f FromIOFS) Open(name string) (File, error) { 172 file, err := f.FS.Open(name) 173 if err != nil { 174 return nil, err 175 } 176 177 return fromIOFSFile{File: file, name: name}, nil 178 } 179 180 func (f FromIOFS) OpenFile(name string, flag int, perm os.FileMode) (File, error) { 181 return f.Open(name) 182 } 183 184 func (f FromIOFS) Remove(name string) error { 185 return notImplemented("remove", name) 186 } 187 188 func (f FromIOFS) RemoveAll(path string) error { 189 return notImplemented("removeall", path) 190 } 191 192 func (f FromIOFS) Rename(oldname, newname string) error { 193 return notImplemented("rename", oldname) 194 } 195 196 func (f FromIOFS) Stat(name string) (os.FileInfo, error) { return fs.Stat(f.FS, name) } 197 198 func (f FromIOFS) Name() string { return "fromiofs" } 199 200 func (f FromIOFS) Chmod(name string, mode os.FileMode) error { 201 return notImplemented("chmod", name) 202 } 203 204 func (f FromIOFS) Chown(name string, uid, gid int) error { 205 return notImplemented("chown", name) 206 } 207 208 func (f FromIOFS) Chtimes(name string, atime time.Time, mtime time.Time) error { 209 return notImplemented("chtimes", name) 210 } 211 212 type fromIOFSFile struct { 213 fs.File 214 name string 215 } 216 217 func (f fromIOFSFile) ReadAt(p []byte, off int64) (n int, err error) { 218 readerAt, ok := f.File.(io.ReaderAt) 219 if !ok { 220 return -1, notImplemented("readat", f.name) 221 } 222 223 return readerAt.ReadAt(p, off) 224 } 225 226 func (f fromIOFSFile) Seek(offset int64, whence int) (int64, error) { 227 seeker, ok := f.File.(io.Seeker) 228 if !ok { 229 return -1, notImplemented("seek", f.name) 230 } 231 232 return seeker.Seek(offset, whence) 233 } 234 235 func (f fromIOFSFile) Write(p []byte) (n int, err error) { 236 return -1, notImplemented("write", f.name) 237 } 238 239 func (f fromIOFSFile) WriteAt(p []byte, off int64) (n int, err error) { 240 return -1, notImplemented("writeat", f.name) 241 } 242 243 func (f fromIOFSFile) Name() string { return f.name } 244 245 func (f fromIOFSFile) Readdir(count int) ([]os.FileInfo, error) { 246 rdfile, ok := f.File.(fs.ReadDirFile) 247 if !ok { 248 return nil, notImplemented("readdir", f.name) 249 } 250 251 entries, err := rdfile.ReadDir(count) 252 if err != nil { 253 return nil, err 254 } 255 256 ret := make([]os.FileInfo, len(entries)) 257 for i := range entries { 258 ret[i], err = entries[i].Info() 259 260 if err != nil { 261 return nil, err 262 } 263 } 264 265 return ret, nil 266 } 267 268 func (f fromIOFSFile) Readdirnames(n int) ([]string, error) { 269 rdfile, ok := f.File.(fs.ReadDirFile) 270 if !ok { 271 return nil, notImplemented("readdir", f.name) 272 } 273 274 entries, err := rdfile.ReadDir(n) 275 if err != nil { 276 return nil, err 277 } 278 279 ret := make([]string, len(entries)) 280 for i := range entries { 281 ret[i] = entries[i].Name() 282 } 283 284 return ret, nil 285 } 286 287 func (f fromIOFSFile) Sync() error { return nil } 288 289 func (f fromIOFSFile) Truncate(size int64) error { 290 return notImplemented("truncate", f.name) 291 } 292 293 func (f fromIOFSFile) WriteString(s string) (ret int, err error) { 294 return -1, notImplemented("writestring", f.name) 295 } 296 297 func notImplemented(op, path string) error { 298 return &fs.PathError{Op: op, Path: path, Err: fs.ErrPermission} 299 }