iofs.go raw

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