├── .travis.yml ├── LICENSE ├── README.md ├── doc.go ├── dummy.go ├── dummy_test.go ├── example_dummy_test.go ├── example_os_test.go ├── example_readonly_test.go ├── example_test.go ├── example_wrapping_test.go ├── filesystem.go ├── filesystem_test.go ├── ioutil.go ├── ioutil_test.go ├── memfs ├── buffer.go ├── buffer_test.go ├── doc.go ├── example_test.go ├── memfile.go ├── memfile_test.go ├── memfs.go └── memfs_test.go ├── mountfs ├── doc.go ├── example_test.go ├── mountfs.go └── mountfs_test.go ├── os.go ├── os_test.go ├── path.go ├── path_test.go ├── prefixfs ├── prefixfs.go └── prefixfs_test.go ├── readonly.go └── readonly_test.go /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | matrix: 3 | include: 4 | - go: 1.4.3 5 | - go: 1.5.4 6 | - go: 1.6.3 7 | - go: 1.7 8 | - go: tip 9 | allow_failures: 10 | - go: tip 11 | install: 12 | - go get golang.org/x/tools/cmd/cover 13 | - go get github.com/mattn/goveralls 14 | script: 15 | - echo "Test and track coverage" ; $HOME/gopath/bin/goveralls -service=travis-ci -repotoken $COVERALLS_TOKEN 16 | - echo "Check if gofmt'd" ; diff -u <(echo -n) <(gofmt -d -s .) 17 | env: 18 | global: 19 | secure: b0jLb8iU21GMhuR6bM0c32LdmCJqUaR6g7y6xUZde6J80cMZh0pSqKF1h6D8u/a9WsaKCXz5lgEeFnxKBDcvlDcjCJJWqsExOr2hBWWssvFGbn0mp0hDKVlg0V3txp/+xm0bix/o9I0rTzqEk2wBVJAyfYyOSqijKZr0FkVOcbrdcw3Bk8A7eZmWXvQPdGfm5sXhEfgV6n3PirPsF3Emw6R0mdcTPqWBpiUvy6zfdCuk3eldw/6QLdaMZuZvegTOiq3w18LB2H9d72VY09ZhvSyQOBHCm8EHOEu21m6g311wsoiNKI1b7/m0NovAmmlddpijRgaiwQ53L535IU4rX5jCi7P8ZoJVPkkSdY3H88ljad5syWaj/pradJcb9j119Zzg6EVyDff0i02D4S716Vb+XSdcDWmlM/YORfA7WurLjWRmd6dPuALLDFdqBjVF49sHB0ZFYHDbcprsl+1jLvIXZQKtCH22xCG9AdCS/5g0QL4tkXnIxw5LT6ZMBZN8occjFBB1Dgy75/Fhlue0TgrIrxzLo/hzLgu0pMYEKu/GS8B31Cis7WjLHoaSb7FVXTfmHElSAhax/EgfYtk5g2tKdnssH0j7HQBcuKPpZOP8UXcB0oDzQ4dDu/IAu280bTWqf4ej2iOjhKlxdDKSq5lSQsXblM5NV6gQQW1Apms= 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2015 Benedikt Lang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | vfs for golang [![Build Status](https://travis-ci.org/blang/vfs.svg?branch=master)](https://travis-ci.org/blang/vfs) [![GoDoc](https://godoc.org/github.com/blang/vfs?status.png)](https://godoc.org/github.com/blang/vfs) [![Coverage Status](https://img.shields.io/coveralls/blang/vfs.svg)](https://coveralls.io/r/blang/vfs?branch=master) [![Join the chat at https://gitter.im/blang/vfs](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/blang/vfs?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 2 | ====== 3 | 4 | vfs is library to support virtual filesystems. It provides basic abstractions of filesystems and implementations, like `OS` accessing the file system of the underlying OS and `memfs` a full filesystem in-memory. 5 | 6 | Usage 7 | ----- 8 | ```bash 9 | $ go get github.com/blang/vfs 10 | ``` 11 | Note: Always vendor your dependencies or fix on a specific version tag. 12 | 13 | ```go 14 | import github.com/blang/vfs 15 | ``` 16 | 17 | ```go 18 | // Create a vfs accessing the filesystem of the underlying OS 19 | var osfs vfs.Filesystem = vfs.OS() 20 | osfs.Mkdir("/tmp", 0777) 21 | 22 | // Make the filesystem read-only: 23 | osfs = vfs.ReadOnly(osfs) // Simply wrap filesystems to change its behaviour 24 | 25 | // os.O_CREATE will fail and return vfs.ErrReadOnly 26 | // os.O_RDWR is supported but Write(..) on the file is disabled 27 | f, _ := osfs.OpenFile("/tmp/example.txt", os.O_RDWR, 0) 28 | 29 | // Return vfs.ErrReadOnly 30 | _, err := f.Write([]byte("Write on readonly fs?")) 31 | if err != nil { 32 | fmt.Errorf("Filesystem is read only!\n") 33 | } 34 | 35 | // Create a fully writable filesystem in memory 36 | mfs := memfs.Create() 37 | mfs.Mkdir("/root", 0777) 38 | 39 | // Create a vfs supporting mounts 40 | // The root fs is accessing the filesystem of the underlying OS 41 | fs := mountfs.Create(osfs) 42 | 43 | // Mount a memfs inside /memfs 44 | // /memfs may not exist 45 | fs.Mount(mfs, "/memfs") 46 | 47 | // This will create /testdir inside the memfs 48 | fs.Mkdir("/memfs/testdir", 0777) 49 | 50 | // This would create /tmp/testdir inside your OS fs 51 | // But the rootfs `osfs` is read-only 52 | fs.Mkdir("/tmp/testdir", 0777) 53 | ``` 54 | 55 | Check detailed examples below. Also check the [GoDocs](http://godoc.org/github.com/blang/vfs). 56 | 57 | Why should I use this lib? 58 | ----- 59 | 60 | - Only Stdlib 61 | - (Nearly) Fully tested (Coverage >90%) 62 | - Easy to create your own filesystem 63 | - Mock a full filesystem for testing (or use included `memfs`) 64 | - Compose/Wrap Filesystems `ReadOnly(OS())` and write simple Wrappers 65 | - Many features, see [GoDocs](http://godoc.org/github.com/blang/vfs) and examples below 66 | 67 | Features and Examples 68 | ----- 69 | 70 | - [OS Filesystem support](http://godoc.org/github.com/blang/vfs#example-OsFS) 71 | - [ReadOnly Wrapper](http://godoc.org/github.com/blang/vfs#example-RoFS) 72 | - [DummyFS for quick mocking](http://godoc.org/github.com/blang/vfs#example-DummyFS) 73 | - [MemFS - full in-memory filesystem](http://godoc.org/github.com/blang/vfs/memfs#example-MemFS) 74 | - [MountFS - support mounts across filesystems](http://godoc.org/github.com/blang/vfs/mountfs#example-MountFS) 75 | 76 | Current state: ALPHA 77 | ----- 78 | 79 | While the functionality is quite stable and heavily tested, interfaces are subject to change. 80 | 81 | You need more/less abstraction? Let me know by creating a Issue, thank you. 82 | 83 | Motivation 84 | ----- 85 | 86 | I simply couldn't find any lib supporting this wide range of variation and adaptability. 87 | 88 | Contribution 89 | ----- 90 | 91 | Feel free to make a pull request. For bigger changes create a issue first to discuss about it. 92 | 93 | License 94 | ----- 95 | 96 | See [LICENSE](LICENSE) file. 97 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Package vfs defines an abstract file system and various implementations. 2 | package vfs 3 | -------------------------------------------------------------------------------- /dummy.go: -------------------------------------------------------------------------------- 1 | package vfs 2 | 3 | import ( 4 | "os" 5 | "time" 6 | ) 7 | 8 | // Dummy creates a new dummy filesystem which returns the given error on every operation. 9 | func Dummy(err error) *DummyFS { 10 | return &DummyFS{err} 11 | } 12 | 13 | // DummyFS is dummy filesystem which returns an error on every operation. 14 | // It can be used to mock a full filesystem for testing or fs creation. 15 | type DummyFS struct { 16 | err error 17 | } 18 | 19 | // PathSeparator returns the path separator 20 | func (fs DummyFS) PathSeparator() uint8 { 21 | return '/' 22 | } 23 | 24 | // OpenFile returns dummy error 25 | func (fs DummyFS) OpenFile(name string, flag int, perm os.FileMode) (File, error) { 26 | return nil, fs.err 27 | } 28 | 29 | // Remove returns dummy error 30 | func (fs DummyFS) Remove(name string) error { 31 | return fs.err 32 | } 33 | 34 | // Rename returns dummy error 35 | func (fs DummyFS) Rename(oldpath, newpath string) error { 36 | return fs.err 37 | } 38 | 39 | // Mkdir returns dummy error 40 | func (fs DummyFS) Mkdir(name string, perm os.FileMode) error { 41 | return fs.err 42 | } 43 | 44 | // Stat returns dummy error 45 | func (fs DummyFS) Stat(name string) (os.FileInfo, error) { 46 | return nil, fs.err 47 | } 48 | 49 | // Lstat returns dummy error 50 | func (fs DummyFS) Lstat(name string) (os.FileInfo, error) { 51 | return nil, fs.err 52 | } 53 | 54 | // ReadDir returns dummy error 55 | func (fs DummyFS) ReadDir(path string) ([]os.FileInfo, error) { 56 | return nil, fs.err 57 | } 58 | 59 | // DummyFile mocks a File returning an error on every operation 60 | // To create a DummyFS returning a dummyFile instead of an error 61 | // you can your own DummyFS: 62 | // 63 | // type writeDummyFS struct { 64 | // Filesystem 65 | // } 66 | // 67 | // func (fs writeDummyFS) OpenFile(name string, flag int, perm os.FileMode) (File, error) { 68 | // return DummyFile(dummyError), nil 69 | // } 70 | func DummyFile(err error) *DumFile { 71 | return &DumFile{err} 72 | } 73 | 74 | // DumFile represents a dummy File 75 | type DumFile struct { 76 | err error 77 | } 78 | 79 | // Name returns "dummy" 80 | func (f DumFile) Name() string { 81 | return "dummy" 82 | } 83 | 84 | // Sync returns dummy error 85 | func (f DumFile) Sync() error { 86 | return f.err 87 | } 88 | 89 | // Truncate returns dummy error 90 | func (f DumFile) Truncate(size int64) error { 91 | return f.err 92 | } 93 | 94 | // Close returns dummy error 95 | func (f DumFile) Close() error { 96 | return f.err 97 | } 98 | 99 | // Write returns dummy error 100 | func (f DumFile) Write(p []byte) (n int, err error) { 101 | return 0, f.err 102 | } 103 | 104 | // Read returns dummy error 105 | func (f DumFile) Read(p []byte) (n int, err error) { 106 | return 0, f.err 107 | } 108 | 109 | // ReadAt returns dummy error 110 | func (f DumFile) ReadAt(p []byte, off int64) (n int, err error) { 111 | return 0, f.err 112 | } 113 | 114 | // Seek returns dummy error 115 | func (f DumFile) Seek(offset int64, whence int) (int64, error) { 116 | return 0, f.err 117 | } 118 | 119 | // DumFileInfo mocks a os.FileInfo returning default values on every operation 120 | // Struct fields can be set. 121 | type DumFileInfo struct { 122 | IName string 123 | ISize int64 124 | IMode os.FileMode 125 | IModTime time.Time 126 | IDir bool 127 | ISys interface{} 128 | } 129 | 130 | // Name returns the field IName 131 | func (fi DumFileInfo) Name() string { 132 | return fi.IName 133 | } 134 | 135 | // Size returns the field ISize 136 | func (fi DumFileInfo) Size() int64 { 137 | return fi.ISize 138 | } 139 | 140 | // Mode returns the field IMode 141 | func (fi DumFileInfo) Mode() os.FileMode { 142 | return fi.IMode 143 | } 144 | 145 | // ModTime returns the field IModTime 146 | func (fi DumFileInfo) ModTime() time.Time { 147 | return fi.IModTime 148 | } 149 | 150 | // IsDir returns the field IDir 151 | func (fi DumFileInfo) IsDir() bool { 152 | return fi.IDir 153 | } 154 | 155 | // Sys returns the field ISys 156 | func (fi DumFileInfo) Sys() interface{} { 157 | return fi.ISys 158 | } 159 | -------------------------------------------------------------------------------- /dummy_test.go: -------------------------------------------------------------------------------- 1 | package vfs 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | ) 7 | 8 | var errDum = errors.New("Dummy error") 9 | 10 | func TestInterface(t *testing.T) { 11 | _ = Filesystem(Dummy(errDum)) 12 | } 13 | 14 | func TestDummyFS(t *testing.T) { 15 | fs := Dummy(errDum) 16 | if _, err := fs.OpenFile("test", 0, 0); err != errDum { 17 | t.Errorf("OpenFile DummyError expected: %s", err) 18 | } 19 | if err := fs.Remove("test"); err != errDum { 20 | t.Errorf("Remove DummyError expected: %s", err) 21 | } 22 | if err := fs.Rename("old", "new"); err != errDum { 23 | t.Errorf("Rename DummyError expected: %s", err) 24 | } 25 | if err := fs.Mkdir("test", 0); err != errDum { 26 | t.Errorf("Mkdir DummyError expected: %s", err) 27 | } 28 | if _, err := fs.Stat("test"); err != errDum { 29 | t.Errorf("Stat DummyError expected: %s", err) 30 | } 31 | if _, err := fs.Lstat("test"); err != errDum { 32 | t.Errorf("Lstat DummyError expected: %s", err) 33 | } 34 | if _, err := fs.ReadDir("test"); err != errDum { 35 | t.Errorf("ReadDir DummyError expected: %s", err) 36 | } 37 | } 38 | 39 | func TestFileInterface(t *testing.T) { 40 | _ = File(DummyFile(errDum)) 41 | } 42 | 43 | func TestDummyFile(t *testing.T) { 44 | f := DummyFile(errDum) 45 | if name := f.Name(); name != "dummy" { 46 | t.Errorf("Invalid name: %s", name) 47 | } 48 | if err := f.Close(); err != errDum { 49 | t.Errorf("Close DummyError expected: %s", err) 50 | } 51 | if _, err := f.Write([]byte("test")); err != errDum { 52 | t.Errorf("Write DummyError expected: %s", err) 53 | } 54 | if _, err := f.Read([]byte{}); err != errDum { 55 | t.Errorf("Read DummyError expected: %s", err) 56 | } 57 | if _, err := f.Seek(0, 0); err != errDum { 58 | t.Errorf("Seek DummyError expected: %s", err) 59 | } 60 | if err := f.Sync(); err != errDum { 61 | t.Errorf("Sync DummyError expected: %s", err) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /example_dummy_test.go: -------------------------------------------------------------------------------- 1 | package vfs_test 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/blang/vfs" 9 | ) 10 | 11 | type myFS struct { 12 | vfs.Filesystem // Embed the Filesystem interface and fill it with vfs.Dummy on creation 13 | } 14 | 15 | func MyFS() *myFS { 16 | return &myFS{ 17 | vfs.Dummy(errors.New("Not implemented yet!")), 18 | } 19 | } 20 | 21 | func (fs myFS) Mkdir(name string, perm os.FileMode) error { 22 | // Create a directory 23 | // ... 24 | return nil 25 | } 26 | 27 | func ExampleDummyFS() { 28 | // Simply bootstrap your filesystem 29 | var fs vfs.Filesystem = MyFS() 30 | 31 | // Your mkdir implementation 32 | fs.Mkdir("/tmp", 0777) 33 | 34 | // All necessary methods like OpenFile (therefor Create) are stubbed 35 | // and return the dummys error 36 | _, err := vfs.Create(fs, "/tmp/vfs/example.txt") 37 | if err != nil { 38 | fmt.Printf("Error will be: Not implemented yet!\n") 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /example_os_test.go: -------------------------------------------------------------------------------- 1 | package vfs_test 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/blang/vfs" 7 | ) 8 | 9 | func ExampleOsFS() { 10 | 11 | // Create a vfs accessing the filesystem of the underlying OS 12 | osFS := vfs.OS() 13 | err := osFS.Mkdir("/tmp/vfs_example", 0777) 14 | if err != nil { 15 | fmt.Printf("Error creating directory: %s\n", err) 16 | } 17 | 18 | // Convenience method 19 | f, err := vfs.Create(osFS, "/tmp/vfs_example/example.txt") 20 | // f, err := osFS.OpenFile("/tmp/vfs/example.txt", os.O_CREATE|os.O_RDWR, 0666) 21 | if err != nil { 22 | fmt.Printf("Could not create file: %s\n", err) 23 | } 24 | defer f.Close() 25 | if _, err := f.Write([]byte("VFS working on your filesystem")); err != nil { 26 | fmt.Printf("Error writing to file: %s\n", err) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /example_readonly_test.go: -------------------------------------------------------------------------------- 1 | package vfs_test 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/blang/vfs" 8 | ) 9 | 10 | // Every vfs.Filesystem could be easily wrapped 11 | func ExampleRoFS() { 12 | // Create a readonly vfs accessing the filesystem of the underlying OS 13 | roFS := vfs.ReadOnly(vfs.OS()) 14 | 15 | // Mkdir is disabled on ReadOnly vfs, will return vfs.ErrReadOnly 16 | // See vfs.ReadOnly for all disabled operations 17 | err := roFS.Mkdir("/tmp/vfs_example", 0777) 18 | if err != nil { 19 | fmt.Printf("Error creating directory: %s\n", err) 20 | return 21 | } 22 | 23 | // OpenFile is controlled to support read-only functionality. os.O_CREATE or os.O_APPEND will fail. 24 | // Flags like os.O_RDWR are supported but the returned file is protected e.g. from Write(..). 25 | f, err := roFS.OpenFile("/tmp/vfs_example/example.txt", os.O_RDWR, 0) 26 | if err != nil { 27 | fmt.Printf("Could not create file: %s\n", err) 28 | return 29 | 30 | } 31 | defer f.Close() 32 | 33 | // Will fail and return vfs.ErrReadOnly 34 | _, err = f.Write([]byte("VFS working on your filesystem")) 35 | if err != nil { 36 | fmt.Printf("Could not write file on read only filesystem: %s", err) 37 | return 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /example_test.go: -------------------------------------------------------------------------------- 1 | package vfs_test 2 | 3 | import ( 4 | "fmt" 5 | "github.com/blang/vfs" 6 | "github.com/blang/vfs/memfs" 7 | "github.com/blang/vfs/mountfs" 8 | "os" 9 | ) 10 | 11 | func Example() { 12 | // Create a vfs accessing the filesystem of the underlying OS 13 | var osfs vfs.Filesystem = vfs.OS() 14 | osfs.Mkdir("/tmp", 0777) 15 | 16 | // Make the filesystem read-only: 17 | osfs = vfs.ReadOnly(osfs) // Simply wrap filesystems to change its behaviour 18 | 19 | // os.O_CREATE will fail and return vfs.ErrReadOnly 20 | // os.O_RDWR is supported but Write(..) on the file is disabled 21 | f, _ := osfs.OpenFile("/tmp/example.txt", os.O_RDWR, 0) 22 | 23 | // Return vfs.ErrReadOnly 24 | _, err := f.Write([]byte("Write on readonly fs?")) 25 | if err != nil { 26 | fmt.Errorf("Filesystem is read only!\n") 27 | } 28 | 29 | // Create a fully writable filesystem in memory 30 | mfs := memfs.Create() 31 | mfs.Mkdir("/root", 0777) 32 | 33 | // Create a vfs supporting mounts 34 | // The root fs is accessing the filesystem of the underlying OS 35 | fs := mountfs.Create(osfs) 36 | 37 | // Mount a memfs inside /memfs 38 | // /memfs may not exist 39 | fs.Mount(mfs, "/memfs") 40 | 41 | // This will create /testdir inside the memfs 42 | fs.Mkdir("/memfs/testdir", 0777) 43 | 44 | // This would create /tmp/testdir inside your OS fs 45 | // But the rootfs `osfs` is read-only 46 | fs.Mkdir("/tmp/testdir", 0777) 47 | } 48 | -------------------------------------------------------------------------------- /example_wrapping_test.go: -------------------------------------------------------------------------------- 1 | package vfs_test 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/blang/vfs" 9 | ) 10 | 11 | type noNewDirs struct { 12 | vfs.Filesystem 13 | } 14 | 15 | func NoNewDirs(fs vfs.Filesystem) *noNewDirs { 16 | return &noNewDirs{fs} 17 | } 18 | 19 | // Mkdir is disabled 20 | func (fs *noNewDirs) Mkdir(name string, perm os.FileMode) error { 21 | return errors.New("Mkdir disabled!") 22 | } 23 | 24 | func ExampleOsFS_myWrapper() { 25 | 26 | // Disable Mkdirs on the OS Filesystem 27 | var fs vfs.Filesystem = NoNewDirs(vfs.OS()) 28 | 29 | err := fs.Mkdir("/tmp", 0777) 30 | if err != nil { 31 | fmt.Printf("Mkdir disabled!\n") 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /filesystem.go: -------------------------------------------------------------------------------- 1 | package vfs 2 | 3 | import ( 4 | "errors" 5 | "io" 6 | "os" 7 | "strings" 8 | ) 9 | 10 | var ( 11 | // ErrIsDirectory is returned if a file is a directory 12 | ErrIsDirectory = errors.New("Is directory") 13 | // ErrNotDirectory is returned if a file is not a directory 14 | ErrNotDirectory = errors.New("Is not a directory") 15 | ) 16 | 17 | // Filesystem represents an abstract filesystem 18 | type Filesystem interface { 19 | PathSeparator() uint8 20 | OpenFile(name string, flag int, perm os.FileMode) (File, error) 21 | Remove(name string) error 22 | // RemoveAll(path string) error 23 | Rename(oldpath, newpath string) error 24 | Mkdir(name string, perm os.FileMode) error 25 | // Symlink(oldname, newname string) error 26 | // TempDir() string 27 | // Chmod(name string, mode FileMode) error 28 | // Chown(name string, uid, gid int) error 29 | Stat(name string) (os.FileInfo, error) 30 | Lstat(name string) (os.FileInfo, error) 31 | ReadDir(path string) ([]os.FileInfo, error) 32 | } 33 | 34 | // File represents a File with common operations. 35 | // It differs from os.File so e.g. Stat() needs to be called from the Filesystem instead. 36 | // osfile.Stat() -> filesystem.Stat(file.Name()) 37 | type File interface { 38 | Name() string 39 | Sync() error 40 | // Truncate shrinks or extends the size of the File to the specified size. 41 | Truncate(int64) error 42 | io.Reader 43 | io.ReaderAt 44 | io.Writer 45 | io.Seeker 46 | io.Closer 47 | } 48 | 49 | // Create creates the named file mode 0666 (before umask) on the given Filesystem, 50 | // truncating it if it already exists. 51 | // The associated file descriptor has mode os.O_RDWR. 52 | // If there is an error, it will be of type *os.PathError. 53 | func Create(fs Filesystem, name string) (File, error) { 54 | return fs.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666) 55 | } 56 | 57 | // Open opens the named file on the given Filesystem for reading. 58 | // If successful, methods on the returned file can be used for reading. 59 | // The associated file descriptor has mode os.O_RDONLY. 60 | // If there is an error, it will be of type *PathError. 61 | func Open(fs Filesystem, name string) (File, error) { 62 | return fs.OpenFile(name, os.O_RDONLY, 0) 63 | } 64 | 65 | // MkdirAll creates a directory named path on the given Filesystem, 66 | // along with any necessary parents, and returns nil, 67 | // or else returns an error. 68 | // The permission bits perm are used for all 69 | // directories that MkdirAll creates. 70 | // If path is already a directory, MkdirAll does nothing 71 | // and returns nil. 72 | func MkdirAll(fs Filesystem, path string, perm os.FileMode) error { 73 | if dir, err := fs.Stat(path); err == nil { 74 | if dir.IsDir() { 75 | return nil 76 | } 77 | return &os.PathError{"mkdir", path, ErrNotDirectory} 78 | } 79 | 80 | parts := SplitPath(path, string(fs.PathSeparator())) 81 | if len(parts) > 1 { 82 | // Create parent 83 | err := MkdirAll(fs, strings.Join(parts[0:len(parts)-1], string(fs.PathSeparator())), perm) 84 | if err != nil { 85 | return err 86 | } 87 | } 88 | 89 | // Parent now exists; invoke Mkdir and use its result. 90 | err := fs.Mkdir(path, perm) 91 | if err != nil { 92 | // Handle arguments like "foo/." by 93 | // double-checking that directory doesn't exist. 94 | dir, err1 := fs.Lstat(path) 95 | if err1 == nil && dir.IsDir() { 96 | return nil 97 | } 98 | return err 99 | } 100 | return nil 101 | } 102 | 103 | // RemoveAll removes path and any children it contains. 104 | // It removes everything it can but returns the first error 105 | // it encounters. If the path does not exist, RemoveAll 106 | // returns nil. 107 | func RemoveAll(fs Filesystem, path string) error { 108 | if err := fs.Remove(path); err == nil || os.IsNotExist(err) { 109 | return nil 110 | } 111 | 112 | // We could not delete it, so might be a directory 113 | fis, err := fs.ReadDir(path) 114 | if err != nil { 115 | if os.IsNotExist(err) { 116 | return nil 117 | } 118 | return err 119 | } 120 | 121 | // Remove contents & return first error. 122 | err = nil 123 | for _, fi := range fis { 124 | err1 := RemoveAll(fs, path+string(fs.PathSeparator())+fi.Name()) 125 | if err == nil { 126 | err = err1 127 | } 128 | } 129 | 130 | // Remove directory itself. 131 | err1 := fs.Remove(path) 132 | if err1 == nil || os.IsNotExist(err1) { 133 | return nil 134 | } 135 | if err == nil { 136 | err = err1 137 | } 138 | return err 139 | } 140 | -------------------------------------------------------------------------------- /filesystem_test.go: -------------------------------------------------------------------------------- 1 | package vfs 2 | 3 | import ( 4 | "errors" 5 | "os" 6 | "testing" 7 | ) 8 | 9 | type openFS struct { 10 | Filesystem 11 | fn func(name string, flag int, perm os.FileMode) (File, error) 12 | } 13 | 14 | func (fs openFS) OpenFile(name string, flag int, perm os.FileMode) (File, error) { 15 | return fs.fn(name, flag, perm) 16 | } 17 | 18 | func TestCreate(t *testing.T) { 19 | fs := openFS{ 20 | Filesystem: Dummy(errors.New("Not implemented")), 21 | fn: func(name string, flag int, perm os.FileMode) (File, error) { 22 | if name != "name" { 23 | t.Errorf("Invalid name: %s", name) 24 | } 25 | if flag != os.O_RDWR|os.O_CREATE|os.O_TRUNC { 26 | t.Errorf("Invalid flag: %d", flag) 27 | } 28 | if perm != 0666 { 29 | t.Errorf("Invalid perm: %d", perm) 30 | } 31 | return nil, nil 32 | }, 33 | } 34 | _, err := Create(fs, "name") 35 | if err != nil { 36 | t.Fatalf("OpenFile not called") 37 | } 38 | } 39 | 40 | func TestOpen(t *testing.T) { 41 | fs := openFS{ 42 | Filesystem: Dummy(errors.New("Not implemented")), 43 | fn: func(name string, flag int, perm os.FileMode) (File, error) { 44 | if name != "name" { 45 | t.Errorf("Invalid name: %s", name) 46 | } 47 | if flag != os.O_RDONLY { 48 | t.Errorf("Invalid flag: %d", flag) 49 | } 50 | if perm != 0 { 51 | t.Errorf("Invalid perm: %d", perm) 52 | } 53 | return nil, nil 54 | }, 55 | } 56 | _, err := Open(fs, "name") 57 | if err != nil { 58 | t.Fatalf("OpenFile not called") 59 | } 60 | } 61 | 62 | type mkdirFS struct { 63 | Filesystem 64 | dirs map[string]os.FileInfo 65 | perm os.FileMode 66 | } 67 | 68 | func (fs *mkdirFS) Mkdir(name string, perm os.FileMode) error { 69 | if _, ok := fs.dirs[name]; ok { 70 | return os.ErrExist 71 | } 72 | fs.perm = perm 73 | fs.dirs[name] = DumFileInfo{ 74 | IName: name, 75 | IDir: true, 76 | IMode: perm, 77 | } 78 | return nil 79 | } 80 | 81 | func (fs mkdirFS) Stat(name string) (os.FileInfo, error) { 82 | return fs.Lstat(name) 83 | } 84 | 85 | func (fs mkdirFS) Lstat(name string) (os.FileInfo, error) { 86 | if fi, ok := fs.dirs[name]; ok { 87 | return fi, nil 88 | } 89 | return nil, os.ErrNotExist 90 | } 91 | 92 | func TestMkdirAll(t *testing.T) { 93 | fs := &mkdirFS{ 94 | Filesystem: Dummy(errors.New("Not implemented")), 95 | dirs: make(map[string]os.FileInfo), 96 | } 97 | err := MkdirAll(fs, "/usr/src/linux", 0777) 98 | if err != nil { 99 | t.Fatalf("Mkdir failed") 100 | } 101 | if fs.perm != 0777 { 102 | t.Errorf("Wrong perm: %d", fs.perm) 103 | } 104 | if _, ok := fs.dirs["/usr"]; !ok { 105 | t.Errorf("Dir not created: /usr") 106 | } 107 | if _, ok := fs.dirs["/usr/src"]; !ok { 108 | t.Errorf("Dir not created: /usr/src") 109 | } 110 | if _, ok := fs.dirs["/usr/src/linux"]; !ok { 111 | t.Errorf("Dir not created: /usr/src/linux") 112 | } 113 | } 114 | 115 | func TestMkdirAllExists(t *testing.T) { 116 | fs := &mkdirFS{ 117 | Filesystem: Dummy(errors.New("Not implemented")), 118 | dirs: make(map[string]os.FileInfo), 119 | } 120 | // Make dir 121 | fs.dirs["/usr/src/linux"] = DumFileInfo{IName: "linux", IDir: true} 122 | 123 | err := MkdirAll(fs, "/usr/src/linux", 0777) 124 | if err != nil { 125 | t.Fatalf("Mkdir failed") 126 | } 127 | } 128 | 129 | func TestMkdirAllFirstExists(t *testing.T) { 130 | fs := &mkdirFS{ 131 | Filesystem: Dummy(errors.New("Not implemented")), 132 | dirs: make(map[string]os.FileInfo), 133 | } 134 | // Make dir 135 | fs.dirs["/usr"] = DumFileInfo{IName: "usr", IDir: true} 136 | 137 | err := MkdirAll(fs, "/usr/src/linux/", 0777) 138 | if err != nil { 139 | t.Fatalf("Mkdir failed") 140 | } 141 | 142 | if _, ok := fs.dirs["/usr/src"]; !ok { 143 | t.Errorf("Dir not created: /usr/src") 144 | } 145 | if _, ok := fs.dirs["/usr/src/linux/"]; !ok { 146 | t.Errorf("Dir not created: /usr/src/linux") 147 | } 148 | } 149 | 150 | func TestMkdirAllFirstExistsNoFile(t *testing.T) { 151 | fs := &mkdirFS{ 152 | Filesystem: Dummy(errors.New("Not implemented")), 153 | dirs: make(map[string]os.FileInfo), 154 | } 155 | // Make dir 156 | fs.dirs["/usr"] = DumFileInfo{IName: "usr", IDir: true} 157 | fs.dirs["/usr/src"] = DumFileInfo{IName: "src", IDir: false} 158 | 159 | err := MkdirAll(fs, "/usr/src/linux/linux-4.10", 0777) 160 | if err == nil { 161 | t.Fatalf("Mkdir failed") 162 | } 163 | } 164 | 165 | type rmFileinfo struct { 166 | DumFileInfo 167 | subfiles []*rmFileinfo 168 | parent *rmFileinfo 169 | } 170 | 171 | type rmFS struct { 172 | Filesystem 173 | files map[string]*rmFileinfo 174 | } 175 | 176 | func (fs *rmFS) ReadDir(path string) ([]os.FileInfo, error) { 177 | if fi, ok := fs.files[path]; ok { 178 | if fi.IsDir() { 179 | s := make([]os.FileInfo, len(fi.subfiles)) 180 | for i, sf := range fi.subfiles { 181 | 182 | s[i] = sf 183 | } 184 | for _, sf := range s { 185 | if sf == nil { 186 | panic("sf in readdir nil") 187 | } 188 | } 189 | return s, nil 190 | } 191 | return nil, ErrNotDirectory 192 | } 193 | 194 | return nil, os.ErrNotExist 195 | } 196 | 197 | func findRmFileInfoIndex(s []*rmFileinfo, needle *rmFileinfo) int { 198 | for i, fi := range s { 199 | if fi == needle { 200 | return i 201 | } 202 | } 203 | return -1 204 | } 205 | 206 | func (fs *rmFS) Remove(name string) error { 207 | if fi, ok := fs.files[name]; ok { 208 | if fi.IsDir() && len(fi.subfiles) > 0 { 209 | return ErrIsDirectory // Not empty 210 | } 211 | 212 | // remove references 213 | delete(fs.files, name) 214 | if fi.parent != nil { 215 | if i := findRmFileInfoIndex(fi.parent.subfiles, fi); i >= 0 { 216 | fi.parent.subfiles = append(fi.parent.subfiles[:i], fi.parent.subfiles[i+1:]...) 217 | } 218 | } 219 | return nil 220 | } 221 | 222 | return &os.PathError{"remove", name, os.ErrNotExist} 223 | } 224 | 225 | func TestRemoveAll(t *testing.T) { 226 | fs := &rmFS{ 227 | Filesystem: Dummy(errors.New("Not implemented")), 228 | files: make(map[string]*rmFileinfo), 229 | } 230 | 231 | fiTmpFile := &rmFileinfo{ 232 | DumFileInfo: DumFileInfo{ 233 | IName: "file", 234 | IDir: false, 235 | }, 236 | } 237 | 238 | fiTmp := &rmFileinfo{ 239 | DumFileInfo: DumFileInfo{ 240 | IName: "tmp", 241 | IDir: true, 242 | }, 243 | subfiles: []*rmFileinfo{ 244 | fiTmpFile, 245 | }, 246 | } 247 | 248 | fiRoot := &rmFileinfo{ 249 | DumFileInfo: DumFileInfo{ 250 | IName: "/", 251 | IDir: true, 252 | }, 253 | subfiles: []*rmFileinfo{ 254 | fiTmp, 255 | }, 256 | } 257 | fs.files["/tmp/file"] = fiTmpFile 258 | fiTmpFile.parent = fiTmp 259 | fs.files["/tmp"] = fiTmp 260 | fiTmp.parent = fiRoot 261 | fs.files["/"] = fiRoot 262 | 263 | fiTmpFile.Name() 264 | err := RemoveAll(fs, "/tmp") 265 | if err != nil { 266 | t.Errorf("Unexpected error remove all: %s", err) 267 | } 268 | 269 | if _, ok := fs.files["/tmp/file"]; ok { 270 | t.Errorf("/tmp/file was not removed") 271 | } 272 | 273 | if _, ok := fs.files["/tmp"]; ok { 274 | t.Errorf("/tmp was not removed") 275 | } 276 | 277 | if _, ok := fs.files["/"]; !ok { 278 | t.Errorf("/ was removed") 279 | } 280 | 281 | } 282 | -------------------------------------------------------------------------------- /ioutil.go: -------------------------------------------------------------------------------- 1 | package vfs 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "os" 7 | ) 8 | 9 | // WriteFile writes data to a file named by filename on the given Filesystem. If 10 | // the file does not exist, WriteFile creates it with permissions perm; 11 | // otherwise WriteFile truncates it before writing. 12 | // 13 | // This is a port of the stdlib ioutil.WriteFile function. 14 | func WriteFile(fs Filesystem, filename string, data []byte, perm os.FileMode) error { 15 | f, err := fs.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm) 16 | if err != nil { 17 | return err 18 | } 19 | n, err := f.Write(data) 20 | if err == nil && n < len(data) { 21 | err = io.ErrShortWrite 22 | } 23 | if err1 := f.Close(); err == nil { 24 | err = err1 25 | } 26 | return err 27 | } 28 | 29 | // ReadFile reads the file named by filename and returns the contents. A 30 | // successful call returns err == nil, not err == EOF. Because ReadFile reads 31 | // the whole file, it does not treat an EOF from Read as an error to be 32 | // reported. 33 | // 34 | // This is a port of the stdlib ioutil.ReadFile function. 35 | func ReadFile(fs Filesystem, filename string) ([]byte, error) { 36 | f, err := fs.OpenFile(filename, os.O_RDONLY, 0) 37 | if err != nil { 38 | return nil, err 39 | } 40 | defer f.Close() 41 | 42 | // It's a good but not certain bet that FileInfo will tell us exactly how 43 | // much to read, so let's try it but be prepared for the answer to be wrong. 44 | var n int64 45 | if fi, err := fs.Stat(filename); err == nil { 46 | if size := fi.Size(); size < 1e9 { 47 | n = size 48 | } 49 | } 50 | 51 | // As initial capacity for readAll, use n + a little extra in case Size is 52 | // zero, and to avoid another allocation after Read has filled the buffer. 53 | // The readAll call will read into its allocated internal buffer cheaply. If 54 | // the size was wrong, we'll either waste some space off the end or 55 | // reallocate as needed, but in the overwhelmingly common case we'll get it 56 | // just right. 57 | return readAll(f, n+bytes.MinRead) 58 | } 59 | 60 | // readAll reads from r until an error or EOF and returns the data it read from 61 | // the internal buffer allocated with a specified capacity. 62 | // 63 | // This is a paste of the stdlib ioutil.readAll function. 64 | func readAll(r io.Reader, capacity int64) (b []byte, err error) { 65 | buf := bytes.NewBuffer(make([]byte, 0, capacity)) 66 | 67 | // If the buffer overflows, we will get bytes.ErrTooLarge. 68 | // Return that as an error. Any other panic remains. 69 | defer func() { 70 | e := recover() 71 | if e == nil { 72 | return 73 | } 74 | if panicErr, ok := e.(error); ok && panicErr == bytes.ErrTooLarge { 75 | err = panicErr 76 | } else { 77 | panic(e) 78 | } 79 | }() 80 | 81 | _, err = buf.ReadFrom(r) 82 | return buf.Bytes(), err 83 | } 84 | -------------------------------------------------------------------------------- /ioutil_test.go: -------------------------------------------------------------------------------- 1 | package vfs_test 2 | 3 | import ( 4 | "bytes" 5 | "os" 6 | "testing" 7 | 8 | "github.com/blang/vfs" 9 | "github.com/blang/vfs/memfs" 10 | ) 11 | 12 | var ( 13 | testpath = "/example.txt" 14 | testmode = os.FileMode(0600) 15 | testdata = bytes.Repeat([]byte("abcdefghijklmnopqrstuvwxyz"), 100) 16 | ) 17 | 18 | func TestWriteFile(t *testing.T) { 19 | fs := memfs.Create() 20 | 21 | vfs.WriteFile(fs, testpath, testdata, testmode) 22 | 23 | info, err := fs.Stat(testpath) 24 | if err != nil { 25 | t.Fatalf("File not created") 26 | } 27 | if info.Size() != int64(len(testdata)) { 28 | t.Fatalf("Bad file size: %d bytes (expected %d)", info.Size(), len(testdata)) 29 | } 30 | if info.Mode() != testmode { 31 | t.Fatalf("Bad file mode: %o (expected %o)", info.Mode(), testmode) 32 | } 33 | } 34 | 35 | func TestReadFile(t *testing.T) { 36 | fs := memfs.Create() 37 | 38 | f, _ := fs.OpenFile(testpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, testmode) 39 | f.Write(testdata) 40 | f.Close() 41 | 42 | data, err := vfs.ReadFile(fs, testpath) 43 | if err != nil { 44 | t.Fatalf("ReadFile failed: %s", err) 45 | } 46 | if len(data) != len(testdata) { 47 | t.Fatalf("Bad data length: %d bytes (expected %d)", len(data), len(testdata)) 48 | } 49 | 50 | _, err = vfs.ReadFile(fs, "/doesnt-exist.txt") 51 | if err == nil { 52 | t.Fatalf("ReadFile failed: expected error") 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /memfs/buffer.go: -------------------------------------------------------------------------------- 1 | package memfs 2 | 3 | import ( 4 | "errors" 5 | "io" 6 | "os" 7 | ) 8 | 9 | // Buffer is a usable block of data similar to a file 10 | type Buffer interface { 11 | io.Reader 12 | io.ReaderAt 13 | io.Writer 14 | io.Seeker 15 | io.Closer 16 | // Truncate shrinks or extends the size of the Buffer to the specified size. 17 | Truncate(int64) error 18 | } 19 | 20 | // MinBufferSize is the minimal initial allocated buffer size 21 | const MinBufferSize = 512 22 | 23 | // ErrTooLarge is thrown if it was not possible to enough memory 24 | var ErrTooLarge = errors.New("Volume too large") 25 | 26 | // Buf is a Buffer working on a slice of bytes. 27 | type Buf struct { 28 | buf *[]byte 29 | ptr int64 30 | } 31 | 32 | // NewBuffer creates a new data volume based on a buffer 33 | func NewBuffer(buf *[]byte) *Buf { 34 | return &Buf{ 35 | buf: buf, 36 | } 37 | } 38 | 39 | // Seek sets the offset for the next Read or Write on the buffer to offset, 40 | // interpreted according to whence: 41 | // 0 (os.SEEK_SET) means relative to the origin of the file 42 | // 1 (os.SEEK_CUR) means relative to the current offset 43 | // 2 (os.SEEK_END) means relative to the end of the file 44 | // It returns the new offset and an error, if any. 45 | func (v *Buf) Seek(offset int64, whence int) (int64, error) { 46 | var abs int64 47 | switch whence { 48 | case os.SEEK_SET: // Relative to the origin of the file 49 | abs = offset 50 | case os.SEEK_CUR: // Relative to the current offset 51 | abs = int64(v.ptr) + offset 52 | case os.SEEK_END: // Relative to the end 53 | abs = int64(len(*v.buf)) + offset 54 | default: 55 | return 0, errors.New("Seek: invalid whence") 56 | } 57 | if abs < 0 { 58 | return 0, errors.New("Seek: negative position") 59 | } 60 | if abs > int64(len(*v.buf)) { 61 | return 0, errors.New("Seek: too far") 62 | } 63 | v.ptr = abs 64 | return abs, nil 65 | } 66 | 67 | // Write writes len(p) byte to the Buffer. 68 | // It returns the number of bytes written and an error if any. 69 | // Write returns non-nil error when n!=len(p). 70 | func (v *Buf) Write(p []byte) (int, error) { 71 | l := len(p) 72 | writeEnd := int(v.ptr) + l - len(*v.buf) 73 | if writeEnd > 0 { 74 | err := v.grow(writeEnd) 75 | if err != nil { 76 | return 0, err 77 | } 78 | } 79 | copy((*v.buf)[v.ptr:], p) 80 | v.ptr += int64(l) 81 | return l, nil 82 | } 83 | 84 | // Close the buffer. Currently no effect. 85 | func (v *Buf) Close() error { 86 | return nil 87 | } 88 | 89 | // Read reads len(p) byte from the Buffer starting at the current offset. 90 | // It returns the number of bytes read and an error if any. 91 | // Returns io.EOF error if pointer is at the end of the Buffer. 92 | func (v *Buf) Read(p []byte) (n int, err error) { 93 | if len(p) == 0 { 94 | return 0, nil 95 | } 96 | if v.ptr >= int64(len(*v.buf)) { 97 | return 0, io.EOF 98 | } 99 | 100 | n = copy(p, (*v.buf)[v.ptr:]) 101 | v.ptr += int64(n) 102 | return 103 | } 104 | 105 | // ReadAt reads len(b) bytes from the Buffer starting at byte offset off. 106 | // It returns the number of bytes read and the error, if any. 107 | // ReadAt always returns a non-nil error when n < len(b). 108 | // At end of file, that error is io.EOF. 109 | func (v *Buf) ReadAt(p []byte, off int64) (n int, err error) { 110 | if len(p) == 0 { 111 | return 0, nil 112 | } 113 | if off >= int64(len(*v.buf)) { 114 | return 0, io.EOF 115 | } 116 | 117 | n = copy(p, (*v.buf)[off:]) 118 | if n < len(p) { 119 | err = io.EOF 120 | } 121 | return 122 | } 123 | 124 | // Truncate truncates the Buffer to a given size. 125 | // It returns an error if the given size is negative. 126 | // If the Buffer is larger than the specified size, the extra data is lost. 127 | // If the Buffer is smaller, it is extended and the extended part (hole) 128 | // reads as zero bytes. 129 | func (v *Buf) Truncate(size int64) (err error) { 130 | if size < 0 { 131 | return errors.New("Truncate: size must be non-negative") 132 | } 133 | if bufSize := int64(len(*v.buf)); size == bufSize { 134 | return nil 135 | } else if size < bufSize { 136 | *v.buf = (*v.buf)[:size] 137 | } else /* size > bufSize */ { 138 | growSize := int(size - bufSize) 139 | if err = v.grow(growSize); err != nil { 140 | return err 141 | } 142 | } 143 | return nil 144 | } 145 | 146 | func (v *Buf) grow(n int) error { 147 | m := len(*v.buf) 148 | if (m + n) > cap(*v.buf) { 149 | size := 2*cap(*v.buf) + MinBufferSize 150 | if size < m+n { 151 | size = m + n + MinBufferSize 152 | } 153 | buf, err := makeSlice(size) 154 | if err != nil { 155 | return err 156 | } 157 | copy(buf, *v.buf) 158 | *v.buf = buf 159 | } 160 | *v.buf = (*v.buf)[0 : m+n] 161 | return nil 162 | } 163 | 164 | // makeSlice allocates a slice of size n. If the allocation fails, it panics 165 | // with ErrTooLarge. 166 | func makeSlice(n int) (b []byte, err error) { 167 | // If the make fails, give a known error. 168 | defer func() { 169 | if recover() != nil { 170 | b = nil 171 | err = ErrTooLarge 172 | return 173 | } 174 | }() 175 | b = make([]byte, n) 176 | return 177 | } 178 | -------------------------------------------------------------------------------- /memfs/buffer_test.go: -------------------------------------------------------------------------------- 1 | package memfs 2 | 3 | import ( 4 | "os" 5 | "reflect" 6 | "strings" 7 | 8 | "io" 9 | "testing" 10 | ) 11 | 12 | const ( 13 | dots = "1....2....3....4" 14 | abc = "abcdefghijklmnop" 15 | ) 16 | 17 | var ( 18 | large = strings.Repeat("0123456789", 200) // 2000 bytes 19 | ) 20 | 21 | func TestWrite(t *testing.T) { 22 | buf := make([]byte, 0, len(dots)) 23 | v := NewBuffer(&buf) 24 | 25 | // Write first dots 26 | if n, err := v.Write([]byte(dots)); err != nil { 27 | t.Errorf("Unexpected error: %s", err) 28 | } else if n != len(dots) { 29 | t.Errorf("Invalid write count: %d", n) 30 | } 31 | if s := string(buf[:len(dots)]); s != dots { 32 | t.Errorf("Invalid buffer content: %q", s) 33 | } 34 | 35 | // Write second time: abc - buffer must grow 36 | if n, err := v.Write([]byte(abc)); err != nil { 37 | t.Errorf("Unexpected error: %s", err) 38 | } else if n != len(abc) { 39 | t.Errorf("Invalid write count: %d", n) 40 | } 41 | if s := string(buf[:len(dots+abc)]); s != dots+abc { 42 | t.Errorf("Invalid buffer content: %q", s) 43 | } 44 | 45 | if len(buf) != len(dots)+len(abc) { 46 | t.Errorf("Origin buffer did not grow: len=%d, cap=%d", len(buf), cap(buf)) 47 | } 48 | 49 | // Test on case when no buffer grow is needed 50 | if n, err := v.Seek(0, os.SEEK_SET); err != nil || n != 0 { 51 | t.Errorf("Invalid seek result: %d %s", n, err) 52 | } 53 | 54 | // Write dots on start of the buffer 55 | if n, err := v.Write([]byte(dots)); err != nil { 56 | t.Errorf("Unexpected error: %s", err) 57 | } else if n != len(dots) { 58 | t.Errorf("Invalid write count: %d", n) 59 | } 60 | if s := string(buf[:len(dots)]); s != dots { 61 | t.Errorf("Invalid buffer content: %q", s) 62 | } 63 | 64 | if len(buf) != len(dots)+len(abc) { 65 | t.Errorf("Origin buffer should not grow: len=%d, cap=%d", len(buf), cap(buf)) 66 | } 67 | 68 | // Restore seek cursor 69 | if n, err := v.Seek(0, os.SEEK_END); err != nil { 70 | t.Errorf("Invalid seek result: %d %s", n, err) 71 | } 72 | 73 | // Can not read, ptr at the end 74 | p := make([]byte, len(dots)) 75 | if n, err := v.Read(p); err == nil || n > 0 { 76 | t.Errorf("Expected read error: %d %s", n, err) 77 | } 78 | 79 | if n, err := v.Seek(0, os.SEEK_SET); err != nil || n != 0 { 80 | t.Errorf("Invalid seek result: %d %s", n, err) 81 | } 82 | 83 | // Read dots 84 | if n, err := v.Read(p); err != nil || n != len(dots) || string(p) != dots { 85 | t.Errorf("Unexpected read error: %d %s, res: %s", n, err, string(p)) 86 | } 87 | 88 | // Read abc 89 | if n, err := v.Read(p); err != nil || n != len(abc) || string(p) != abc { 90 | t.Errorf("Unexpected read error: %d %s, res: %s", n, err, string(p)) 91 | } 92 | 93 | // Seek abc backwards 94 | if n, err := v.Seek(int64(-len(abc)), os.SEEK_END); err != nil || n != int64(len(dots)) { 95 | t.Errorf("Invalid seek result: %d %s", n, err) 96 | } 97 | 98 | // Seek 8 forwards 99 | if n, err := v.Seek(int64(len(abc)/2), os.SEEK_CUR); err != nil || n != int64(16)+int64(len(abc)/2) { 100 | t.Errorf("Invalid seek result: %d %s", n, err) 101 | } 102 | 103 | // Seek to end 104 | if n, err := v.Seek(0, os.SEEK_END); err != nil || n != int64(len(buf)) { 105 | t.Errorf("Invalid seek result: %d %s", n, err) 106 | } 107 | 108 | // Write so that buffer must expand more than 2x 109 | if n, err := v.Write([]byte(large)); err != nil { 110 | t.Errorf("Unexpected error: %s", err) 111 | } else if n != len(large) { 112 | t.Errorf("Invalid write count: %d", n) 113 | } 114 | if s := string(buf[:len(dots+abc+large)]); s != dots+abc+large { 115 | t.Errorf("Invalid buffer content: %q", s) 116 | } 117 | 118 | if len(buf) != len(dots)+len(abc)+len(large) { 119 | t.Errorf("Origin buffer did not grow: len=%d, cap=%d", len(buf), cap(buf)) 120 | } 121 | } 122 | 123 | func TestVolumesConcurrentAccess(t *testing.T) { 124 | buf := make([]byte, 0, len(dots)) 125 | v1 := NewBuffer(&buf) 126 | v2 := NewBuffer(&buf) 127 | 128 | // v1 write dots 129 | if n, err := v1.Write([]byte(dots)); err != nil || n != len(dots) { 130 | t.Errorf("Unexpected write error: %d %s", n, err) 131 | } 132 | 133 | p := make([]byte, len(dots)) 134 | 135 | // v2 read dots 136 | if n, err := v2.Read(p); err != nil || n != len(dots) || string(p) != dots { 137 | t.Errorf("Unexpected read error: %d %s, res: %s", n, err, string(p)) 138 | } 139 | 140 | // v2 write dots 141 | if n, err := v2.Write([]byte(abc)); err != nil || n != len(abc) { 142 | t.Errorf("Unexpected write error: %d %s", n, err) 143 | } 144 | 145 | // v1 read dots 146 | if n, err := v1.Read(p); err != nil || n != len(abc) || string(p) != abc { 147 | t.Errorf("Unexpected read error: %d %s, res: %s", n, err, string(p)) 148 | } 149 | 150 | } 151 | 152 | func TestSeek(t *testing.T) { 153 | buf := make([]byte, 0, len(dots)) 154 | v := NewBuffer(&buf) 155 | 156 | // write dots 157 | if n, err := v.Write([]byte(dots)); err != nil || n != len(dots) { 158 | t.Errorf("Unexpected write error: %d %s", n, err) 159 | } 160 | 161 | // invalid whence 162 | if _, err := v.Seek(0, 4); err == nil { 163 | t.Errorf("Expected invalid whence error") 164 | } 165 | // seek to -1 166 | if _, err := v.Seek(-1, os.SEEK_SET); err == nil { 167 | t.Errorf("Expected invalid position error") 168 | } 169 | 170 | // seek to end 171 | if _, err := v.Seek(0, os.SEEK_END); err != nil { 172 | t.Errorf("Unexpected error: %s", err) 173 | } 174 | 175 | // seel past the end 176 | if _, err := v.Seek(1, os.SEEK_END); err == nil { 177 | t.Errorf("Expected invalid position error") 178 | } 179 | } 180 | 181 | func TestRead(t *testing.T) { 182 | buf := make([]byte, len(dots)) 183 | copy(buf, []byte(dots)) 184 | v := NewBuffer(&buf) 185 | 186 | // p := make([]byte, len(dots)) 187 | var p []byte 188 | 189 | // Read to empty buffer, err==nil, n == 0 190 | if n, err := v.Read(p); err != nil || n > 0 { 191 | t.Errorf("Unexpected read error: %d %s, res: %s", n, err, string(p)) 192 | } 193 | } 194 | 195 | func TestReadAt(t *testing.T) { 196 | buf := make([]byte, len(dots)) 197 | copy(buf, []byte(dots)) 198 | v := NewBuffer(&buf) 199 | 200 | p := make([]byte, len(dots)) 201 | 202 | // Read to empty buffer, err==nil, n == 0 203 | if n, err := v.ReadAt(p[:0], 0); err != nil || n > 0 { 204 | t.Errorf("Unexpected read error: %d %s, res: %s", n, err, string(p)) 205 | } 206 | 207 | // Read the entire buffer, err==nil, n == len(dots) 208 | if n, err := v.ReadAt(p, 0); err != nil || n != len(dots) || string(p[:n]) != dots { 209 | t.Errorf("Unexpected read error: %d %s, res: %s", n, err, string(p)) 210 | } 211 | 212 | // Read the buffer while crossing the end, err==io.EOF, n == len(dots)-1 213 | if n, err := v.ReadAt(p, 1); err != io.EOF || n != len(dots)-1 || string(p[:n]) != dots[1:] { 214 | t.Errorf("Unexpected read error: %d %s, res: %s", n, err, string(p)) 215 | } 216 | 217 | // Read at the buffer's end, err==io.EOF, n == 0 218 | if n, err := v.ReadAt(p, int64(len(dots))); err != io.EOF || n > 0 { 219 | t.Errorf("Unexpected read error: %d %s, res: %s", n, err, string(p)) 220 | } 221 | 222 | // Read past the buffer's end, err==io.EOF, n == 0 223 | if n, err := v.ReadAt(p, int64(len(dots)+1)); err != io.EOF || n > 0 { 224 | t.Errorf("Unexpected read error: %d %s, res: %s", n, err, string(p)) 225 | } 226 | } 227 | 228 | // TestBufferGrowWriteAndSeek tests if Write and Seek inside the 229 | // buffers boundaries result in invalid growth 230 | func TestBufferGrowWriteAndSeek(t *testing.T) { 231 | buf := make([]byte, 0, 3) 232 | v := NewBuffer(&buf) 233 | 234 | writeByte := func(b byte) { 235 | n, err := v.Write([]byte{b}) 236 | if err != nil { 237 | t.Fatalf("Error on write: %s", err) 238 | } else if n != 1 { 239 | t.Fatalf("Unexpected num of bytes written: %d", n) 240 | } 241 | } 242 | 243 | seek := func(off int64, whence int, checkPos int64) { 244 | if n, err := v.Seek(off, whence); err != nil { 245 | t.Fatalf("Error on seek: %s", err) 246 | } else if n != checkPos { 247 | t.Fatalf("Invalid position after seek: %d, expected %d", n, checkPos) 248 | } 249 | } 250 | 251 | // Buffer: [][XXX] 252 | writeByte(0x01) 253 | // Buffer: [1][XX] 254 | writeByte(0x02) 255 | // Buffer: [1,2][X] 256 | seek(0, os.SEEK_SET, 0) // Seek to index 0 257 | writeByte(0x03) 258 | // Buffer: [3,2][X] 259 | seek(0, os.SEEK_END, 2) // Seek to end 260 | writeByte(0x01) 261 | // Buffer: [3,2,1][] 262 | 263 | // Check content of buf 264 | if !reflect.DeepEqual([]byte{0x03, 0x02, 0x01}, buf) { 265 | t.Fatalf("Invalid buffer: %v, len=%d, cap=%d", buf, len(buf), cap(buf)) 266 | } 267 | 268 | // Check for growth 269 | if c := cap(buf); c != 3 { 270 | t.Fatalf("Invalid buffer cap: %d", c) 271 | } 272 | } 273 | -------------------------------------------------------------------------------- /memfs/doc.go: -------------------------------------------------------------------------------- 1 | // Package memfs defines an in-memory filesystem 2 | package memfs 3 | -------------------------------------------------------------------------------- /memfs/example_test.go: -------------------------------------------------------------------------------- 1 | package memfs_test 2 | 3 | import ( 4 | "github.com/blang/vfs/memfs" 5 | ) 6 | 7 | func ExampleMemFS() { 8 | // Create a fully writable filesystem in memory 9 | fs := memfs.Create() 10 | // Like every other vfs.Filesytem, it could be wrapped, e.g. read-only: 11 | // fs = vfs.ReadOnly(fs) 12 | 13 | // The memory fs is completely empty, permissions are supported (e.g. Stat()) but have no effect. 14 | fs.Mkdir("/tmp", 0777) 15 | } 16 | -------------------------------------------------------------------------------- /memfs/memfile.go: -------------------------------------------------------------------------------- 1 | package memfs 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | // MemFile represents a file backed by a Buffer which is secured from concurrent access. 8 | type MemFile struct { 9 | Buffer 10 | mutex *sync.RWMutex 11 | name string 12 | } 13 | 14 | // NewMemFile creates a Buffer which byte slice is safe from concurrent access, 15 | // the file itself is not thread-safe. 16 | // 17 | // This means multiple files can work safely on the same byte slice, 18 | // but multiple go routines working on the same file may corrupt the internal pointer structure. 19 | func NewMemFile(name string, rwMutex *sync.RWMutex, buf *[]byte) *MemFile { 20 | return &MemFile{ 21 | Buffer: NewBuffer(buf), 22 | mutex: rwMutex, 23 | name: name, 24 | } 25 | } 26 | 27 | // Name of the file 28 | func (b MemFile) Name() string { 29 | return b.name 30 | } 31 | 32 | // Sync has no effect 33 | func (b MemFile) Sync() error { 34 | return nil 35 | } 36 | 37 | // Truncate changes the size of the file 38 | func (b MemFile) Truncate(size int64) (err error) { 39 | b.mutex.Lock() 40 | err = b.Buffer.Truncate(size) 41 | b.mutex.Unlock() 42 | return 43 | } 44 | 45 | // Read reads len(p) byte from the underlying buffer starting at the current offset. 46 | // It returns the number of bytes read and an error if any. 47 | // Returns io.EOF error if pointer is at the end of the Buffer. 48 | // See Buf.Read() 49 | func (b *MemFile) Read(p []byte) (n int, err error) { 50 | b.mutex.RLock() 51 | n, err = b.Buffer.Read(p) 52 | b.mutex.RUnlock() 53 | return 54 | } 55 | 56 | // ReadAt reads len(b) bytes from the Buffer starting at byte offset off. 57 | // It returns the number of bytes read and the error, if any. 58 | // ReadAt always returns a non-nil error when n < len(b). 59 | // At end of file, that error is io.EOF. 60 | // See Buf.ReadAt() 61 | func (b *MemFile) ReadAt(p []byte, off int64) (n int, err error) { 62 | b.mutex.RLock() 63 | n, err = b.Buffer.ReadAt(p, off) 64 | b.mutex.RUnlock() 65 | return 66 | } 67 | 68 | // Write writes len(p) byte to the Buffer. 69 | // It returns the number of bytes written and an error if any. 70 | // Write returns non-nil error when n!=len(p). 71 | func (b *MemFile) Write(p []byte) (n int, err error) { 72 | b.mutex.Lock() 73 | n, err = b.Buffer.Write(p) 74 | b.mutex.Unlock() 75 | return 76 | } 77 | 78 | // Seek sets the offset for the next Read or Write on the buffer to offset, 79 | // interpreted according to whence: 80 | // 0 (os.SEEK_SET) means relative to the origin of the file 81 | // 1 (os.SEEK_CUR) means relative to the current offset 82 | // 2 (os.SEEK_END) means relative to the end of the file 83 | // It returns the new offset and an error, if any. 84 | func (b *MemFile) Seek(offset int64, whence int) (n int64, err error) { 85 | b.mutex.RLock() 86 | n, err = b.Buffer.Seek(offset, whence) 87 | b.mutex.RUnlock() 88 | return 89 | } 90 | -------------------------------------------------------------------------------- /memfs/memfile_test.go: -------------------------------------------------------------------------------- 1 | package memfs 2 | 3 | import ( 4 | "github.com/blang/vfs" 5 | "testing" 6 | ) 7 | 8 | func TestFileInterface(t *testing.T) { 9 | _ = vfs.File(NewMemFile("", nil, nil)) 10 | } 11 | -------------------------------------------------------------------------------- /memfs/memfs.go: -------------------------------------------------------------------------------- 1 | package memfs 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "os" 7 | filepath "path" 8 | "sort" 9 | "sync" 10 | "time" 11 | 12 | "github.com/blang/vfs" 13 | ) 14 | 15 | var ( 16 | // ErrReadOnly is returned if the file is read-only and write operations are disabled. 17 | ErrReadOnly = errors.New("File is read-only") 18 | // ErrWriteOnly is returned if the file is write-only and read operations are disabled. 19 | ErrWriteOnly = errors.New("File is write-only") 20 | // ErrIsDirectory is returned if the file under operation is not a regular file but a directory. 21 | ErrIsDirectory = errors.New("Is directory") 22 | ) 23 | 24 | // PathSeparator used to separate path segments 25 | const PathSeparator = "/" 26 | 27 | // MemFS is a in-memory filesystem 28 | type MemFS struct { 29 | root *fileInfo 30 | wd *fileInfo 31 | lock *sync.RWMutex 32 | } 33 | 34 | // Create a new MemFS filesystem which entirely resides in memory 35 | func Create() *MemFS { 36 | root := &fileInfo{ 37 | name: "/", 38 | dir: true, 39 | } 40 | return &MemFS{ 41 | root: root, 42 | wd: root, 43 | lock: &sync.RWMutex{}, 44 | } 45 | } 46 | 47 | type fileInfo struct { 48 | name string 49 | dir bool 50 | mode os.FileMode 51 | parent *fileInfo 52 | size int64 53 | modTime time.Time 54 | fs vfs.Filesystem 55 | childs map[string]*fileInfo 56 | buf *[]byte 57 | mutex *sync.RWMutex 58 | } 59 | 60 | func (fi fileInfo) Sys() interface{} { 61 | return fi.fs 62 | } 63 | 64 | func (fi fileInfo) Size() int64 { 65 | if fi.dir { 66 | return 0 67 | } 68 | fi.mutex.RLock() 69 | l := len(*(fi.buf)) 70 | fi.mutex.RUnlock() 71 | return int64(l) 72 | } 73 | 74 | func (fi fileInfo) IsDir() bool { 75 | return fi.dir 76 | } 77 | 78 | // ModTime returns the modification time. 79 | // Modification time is updated on: 80 | // - Creation 81 | // - Rename 82 | // - Open (except with O_RDONLY) 83 | func (fi fileInfo) ModTime() time.Time { 84 | return fi.modTime 85 | } 86 | 87 | func (fi fileInfo) Mode() os.FileMode { 88 | return fi.mode 89 | } 90 | 91 | func (fi fileInfo) Name() string { 92 | return fi.name 93 | } 94 | 95 | func (fi fileInfo) AbsPath() string { 96 | if fi.parent != nil { 97 | return filepath.Join(fi.parent.AbsPath(), fi.name) 98 | } 99 | return "/" 100 | } 101 | 102 | // PathSeparator returns the path separator 103 | func (fs *MemFS) PathSeparator() uint8 { 104 | return '/' 105 | } 106 | 107 | // Mkdir creates a new directory with given permissions 108 | func (fs *MemFS) Mkdir(name string, perm os.FileMode) error { 109 | fs.lock.Lock() 110 | defer fs.lock.Unlock() 111 | name = filepath.Clean(name) 112 | base := filepath.Base(name) 113 | parent, fi, err := fs.fileInfo(name) 114 | if err != nil { 115 | return &os.PathError{"mkdir", name, err} 116 | } 117 | if fi != nil { 118 | return &os.PathError{"mkdir", name, fmt.Errorf("Directory %q already exists", name)} 119 | } 120 | 121 | fi = &fileInfo{ 122 | name: base, 123 | dir: true, 124 | mode: perm, 125 | parent: parent, 126 | modTime: time.Now(), 127 | fs: fs, 128 | } 129 | parent.childs[base] = fi 130 | return nil 131 | } 132 | 133 | // byName implements sort.Interface 134 | type byName []os.FileInfo 135 | 136 | // Len returns the length of the slice 137 | func (f byName) Len() int { return len(f) } 138 | 139 | // Less sorts slice by Name 140 | func (f byName) Less(i, j int) bool { return f[i].Name() < f[j].Name() } 141 | 142 | // Swap two elements by index 143 | func (f byName) Swap(i, j int) { f[i], f[j] = f[j], f[i] } 144 | 145 | // ReadDir reads the directory named by path and returns a list of sorted directory entries. 146 | func (fs *MemFS) ReadDir(path string) ([]os.FileInfo, error) { 147 | fs.lock.RLock() 148 | defer fs.lock.RUnlock() 149 | 150 | path = filepath.Clean(path) 151 | _, fi, err := fs.fileInfo(path) 152 | if err != nil { 153 | return nil, &os.PathError{"readdir", path, err} 154 | } 155 | if fi == nil || !fi.dir { 156 | return nil, &os.PathError{"readdir", path, vfs.ErrNotDirectory} 157 | } 158 | 159 | fis := make([]os.FileInfo, 0, len(fi.childs)) 160 | for _, e := range fi.childs { 161 | fis = append(fis, e) 162 | } 163 | sort.Sort(byName(fis)) 164 | return fis, nil 165 | } 166 | 167 | func (fs *MemFS) fileInfo(path string) (parent *fileInfo, node *fileInfo, err error) { 168 | path = filepath.Clean(path) 169 | segments := vfs.SplitPath(path, PathSeparator) 170 | 171 | // Shortcut for working directory and root 172 | if len(segments) == 1 { 173 | if segments[0] == "" { 174 | return nil, fs.root, nil 175 | } else if segments[0] == "." { 176 | return fs.wd.parent, fs.wd, nil 177 | } 178 | } 179 | 180 | // Determine root to traverse 181 | parent = fs.root 182 | if segments[0] == "." { 183 | parent = fs.wd 184 | } 185 | segments = segments[1:] 186 | 187 | // Further directories 188 | if len(segments) > 1 { 189 | for _, seg := range segments[:len(segments)-1] { 190 | 191 | if parent.childs == nil { 192 | return nil, nil, os.ErrNotExist 193 | } 194 | if entry, ok := parent.childs[seg]; ok && entry.dir { 195 | parent = entry 196 | } else { 197 | return nil, nil, os.ErrNotExist 198 | } 199 | } 200 | } 201 | 202 | lastSeg := segments[len(segments)-1] 203 | if parent.childs != nil { 204 | if node, ok := parent.childs[lastSeg]; ok { 205 | return parent, node, nil 206 | } 207 | } else { 208 | parent.childs = make(map[string]*fileInfo) 209 | } 210 | 211 | return parent, nil, nil 212 | } 213 | 214 | func hasFlag(flag int, flags int) bool { 215 | return flags&flag == flag 216 | } 217 | 218 | // OpenFile opens a file handle with a specified flag (os.O_RDONLY etc.) and perm (e.g. 0666). 219 | // If success the returned File can be used for I/O. Otherwise an error is returned, which 220 | // is a *os.PathError and can be extracted for further information. 221 | func (fs *MemFS) OpenFile(name string, flag int, perm os.FileMode) (vfs.File, error) { 222 | fs.lock.Lock() 223 | defer fs.lock.Unlock() 224 | 225 | name = filepath.Clean(name) 226 | base := filepath.Base(name) 227 | fiParent, fiNode, err := fs.fileInfo(name) 228 | if err != nil { 229 | return nil, &os.PathError{"open", name, err} 230 | } 231 | 232 | if fiNode == nil { 233 | if !hasFlag(os.O_CREATE, flag) { 234 | return nil, &os.PathError{"open", name, os.ErrNotExist} 235 | } 236 | fiNode = &fileInfo{ 237 | name: base, 238 | dir: false, 239 | mode: perm, 240 | parent: fiParent, 241 | modTime: time.Now(), 242 | fs: fs, 243 | } 244 | fiParent.childs[base] = fiNode 245 | } else { // file exists 246 | if hasFlag(os.O_CREATE|os.O_EXCL, flag) { 247 | return nil, &os.PathError{"open", name, os.ErrExist} 248 | } 249 | if fiNode.dir { 250 | return nil, &os.PathError{"open", name, ErrIsDirectory} 251 | } 252 | } 253 | 254 | if !hasFlag(os.O_RDONLY, flag) { 255 | fiNode.modTime = time.Now() 256 | } 257 | return fiNode.file(flag) 258 | } 259 | 260 | func (fi *fileInfo) file(flag int) (vfs.File, error) { 261 | if fi.buf == nil || hasFlag(os.O_TRUNC, flag) { 262 | buf := make([]byte, 0, MinBufferSize) 263 | fi.buf = &buf 264 | fi.mutex = &sync.RWMutex{} 265 | } 266 | var f vfs.File = NewMemFile(fi.AbsPath(), fi.mutex, fi.buf) 267 | if hasFlag(os.O_APPEND, flag) { 268 | f.Seek(0, os.SEEK_END) 269 | } 270 | if hasFlag(os.O_RDWR, flag) { 271 | return f, nil 272 | } else if hasFlag(os.O_WRONLY, flag) { 273 | f = &woFile{f} 274 | } else { 275 | f = &roFile{f} 276 | } 277 | 278 | return f, nil 279 | } 280 | 281 | // roFile wraps the given file and disables Write(..) operation. 282 | type roFile struct { 283 | vfs.File 284 | } 285 | 286 | // Write is disabled and returns ErrorReadOnly 287 | func (f *roFile) Write(p []byte) (n int, err error) { 288 | return 0, ErrReadOnly 289 | } 290 | 291 | // woFile wraps the given file and disables Read(..) operation. 292 | type woFile struct { 293 | vfs.File 294 | } 295 | 296 | // Read is disabled and returns ErrorWroteOnly 297 | func (f *woFile) Read(p []byte) (n int, err error) { 298 | return 0, ErrWriteOnly 299 | } 300 | 301 | // Remove removes the named file or directory. 302 | // If there is an error, it will be of type *PathError. 303 | func (fs *MemFS) Remove(name string) error { 304 | fs.lock.Lock() 305 | defer fs.lock.Unlock() 306 | 307 | name = filepath.Clean(name) 308 | fiParent, fiNode, err := fs.fileInfo(name) 309 | if err != nil { 310 | return &os.PathError{"remove", name, err} 311 | } 312 | if fiNode == nil { 313 | return &os.PathError{"remove", name, os.ErrNotExist} 314 | } 315 | 316 | delete(fiParent.childs, fiNode.name) 317 | return nil 318 | } 319 | 320 | // Rename renames (moves) a file. 321 | // Handles to the oldpath persist but might return oldpath if Name() is called. 322 | func (fs *MemFS) Rename(oldpath, newpath string) error { 323 | fs.lock.Lock() 324 | defer fs.lock.Unlock() 325 | 326 | // OldPath 327 | oldpath = filepath.Clean(oldpath) 328 | fiOldParent, fiOld, err := fs.fileInfo(oldpath) 329 | if err != nil { 330 | return &os.PathError{"rename", oldpath, err} 331 | } 332 | if fiOld == nil { 333 | return &os.PathError{"rename", oldpath, os.ErrNotExist} 334 | } 335 | 336 | newpath = filepath.Clean(newpath) 337 | fiNewParent, fiNew, err := fs.fileInfo(newpath) 338 | if err != nil { 339 | return &os.PathError{"rename", newpath, err} 340 | } 341 | 342 | if fiNew != nil { 343 | return &os.PathError{"rename", newpath, os.ErrExist} 344 | } 345 | 346 | newBase := filepath.Base(newpath) 347 | 348 | // Relink 349 | delete(fiOldParent.childs, fiOld.name) 350 | fiOld.parent = fiNewParent 351 | fiOld.name = newBase 352 | fiOld.modTime = time.Now() 353 | fiNewParent.childs[fiOld.name] = fiOld 354 | return nil 355 | } 356 | 357 | // Stat returns the FileInfo structure describing the named file. 358 | // If there is an error, it will be of type *PathError. 359 | func (fs *MemFS) Stat(name string) (os.FileInfo, error) { 360 | fs.lock.RLock() 361 | defer fs.lock.RUnlock() 362 | 363 | name = filepath.Clean(name) 364 | // dir, base := filepath.Split(name) 365 | _, fi, err := fs.fileInfo(name) 366 | if err != nil { 367 | return nil, &os.PathError{"stat", name, err} 368 | } 369 | if fi == nil { 370 | return nil, &os.PathError{"stat", name, os.ErrNotExist} 371 | } 372 | return fi, nil 373 | } 374 | 375 | // Lstat returns a FileInfo describing the named file. 376 | // MemFS does not support symbolic links. 377 | // Alias for fs.Stat(name) 378 | func (fs *MemFS) Lstat(name string) (os.FileInfo, error) { 379 | return fs.Stat(name) 380 | } 381 | -------------------------------------------------------------------------------- /memfs/memfs_test.go: -------------------------------------------------------------------------------- 1 | package memfs 2 | 3 | import ( 4 | "io/ioutil" 5 | "os" 6 | "testing" 7 | "time" 8 | 9 | "github.com/blang/vfs" 10 | ) 11 | 12 | func TestInterface(t *testing.T) { 13 | _ = vfs.Filesystem(Create()) 14 | } 15 | 16 | func TestCreate(t *testing.T) { 17 | fs := Create() 18 | // Create file with absolute path 19 | { 20 | f, err := fs.OpenFile("/testfile", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666) 21 | if err != nil { 22 | t.Fatalf("Unexpected error creating file: %s", err) 23 | } 24 | if name := f.Name(); name != "/testfile" { 25 | t.Errorf("Wrong name: %s", name) 26 | } 27 | } 28 | 29 | // Create same file again 30 | { 31 | _, err := fs.OpenFile("/testfile", os.O_RDWR|os.O_CREATE, 0666) 32 | if err != nil { 33 | t.Fatalf("Unexpected error creating file: %s", err) 34 | } 35 | 36 | } 37 | 38 | // Create same file again, but truncate it 39 | { 40 | _, err := fs.OpenFile("/testfile", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666) 41 | if err != nil { 42 | t.Fatalf("Unexpected error creating file: %s", err) 43 | } 44 | } 45 | 46 | // Create same file again with O_CREATE|O_EXCL, which is an error 47 | { 48 | _, err := fs.OpenFile("/testfile", os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666) 49 | if err == nil { 50 | t.Fatalf("Expected error creating file: %s", err) 51 | } 52 | } 53 | 54 | // Create file with unkown parent 55 | { 56 | _, err := fs.OpenFile("/testfile/testfile", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666) 57 | if err == nil { 58 | t.Errorf("Expected error creating file") 59 | } 60 | } 61 | 62 | // Create file with relative path (workingDir == root) 63 | { 64 | f, err := fs.OpenFile("relFile", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666) 65 | if err != nil { 66 | t.Fatalf("Unexpected error creating file: %s", err) 67 | } 68 | if name := f.Name(); name != "/relFile" { 69 | t.Errorf("Wrong name: %s", name) 70 | } 71 | } 72 | 73 | } 74 | 75 | func TestMkdirAbsRel(t *testing.T) { 76 | fs := Create() 77 | 78 | // Create dir with absolute path 79 | { 80 | err := fs.Mkdir("/usr", 0) 81 | if err != nil { 82 | t.Fatalf("Unexpected error creating directory: %s", err) 83 | } 84 | } 85 | 86 | // Create dir with relative path 87 | { 88 | err := fs.Mkdir("home", 0) 89 | if err != nil { 90 | t.Fatalf("Unexpected error creating directory: %s", err) 91 | } 92 | } 93 | 94 | // Create dir twice 95 | { 96 | err := fs.Mkdir("/home", 0) 97 | if err == nil { 98 | t.Fatalf("Expecting error creating directory: %s", "/home") 99 | } 100 | } 101 | } 102 | 103 | func TestMkdirTree(t *testing.T) { 104 | fs := Create() 105 | 106 | err := fs.Mkdir("/home", 0) 107 | if err != nil { 108 | t.Fatalf("Unexpected error creating directory /home: %s", err) 109 | } 110 | 111 | err = fs.Mkdir("/home/blang", 0) 112 | if err != nil { 113 | t.Fatalf("Unexpected error creating directory /home/blang: %s", err) 114 | } 115 | 116 | err = fs.Mkdir("/home/blang/goprojects", 0) 117 | if err != nil { 118 | t.Fatalf("Unexpected error creating directory /home/blang/goprojects: %s", err) 119 | } 120 | 121 | err = fs.Mkdir("/home/johndoe/goprojects", 0) 122 | if err == nil { 123 | t.Errorf("Expected error creating directory with non-existing parent") 124 | } 125 | 126 | //TODO: Subdir of file 127 | } 128 | 129 | func TestReadDir(t *testing.T) { 130 | fs := Create() 131 | dirs := []string{"/home", "/home/linus", "/home/rob", "/home/pike", "/home/blang"} 132 | expectNames := []string{"README.txt", "blang", "linus", "pike", "rob"} 133 | for _, dir := range dirs { 134 | err := fs.Mkdir(dir, 0777) 135 | if err != nil { 136 | t.Fatalf("Unexpected error creating directory %q: %s", dir, err) 137 | } 138 | } 139 | f, err := fs.OpenFile("/home/README.txt", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666) 140 | if err != nil { 141 | t.Fatalf("Unexpected error creating file: %s", err) 142 | } 143 | f.Close() 144 | 145 | fis, err := fs.ReadDir("/home") 146 | if err != nil { 147 | t.Fatalf("Unexpected error readdir: %s", err) 148 | } 149 | if l := len(fis); l != len(expectNames) { 150 | t.Errorf("Wrong size: %q (%d)", fis, l) 151 | } 152 | for i, n := range expectNames { 153 | if fn := fis[i].Name(); fn != n { 154 | t.Errorf("Expected name %q, got %q", n, fn) 155 | } 156 | } 157 | 158 | // Readdir empty directory 159 | if fis, err := fs.ReadDir("/home/blang"); err != nil { 160 | t.Errorf("Error readdir(empty directory): %s", err) 161 | } else if l := len(fis); l > 0 { 162 | t.Errorf("Found entries in non-existing directory: %d", l) 163 | } 164 | 165 | // Readdir file 166 | if _, err := fs.ReadDir("/home/README.txt"); err == nil { 167 | t.Errorf("Expected error readdir(file)") 168 | } 169 | 170 | // Readdir subdir of file 171 | if _, err := fs.ReadDir("/home/README.txt/info"); err == nil { 172 | t.Errorf("Expected error readdir(subdir of file)") 173 | } 174 | 175 | // Readdir non existing directory 176 | if _, err := fs.ReadDir("/usr"); err == nil { 177 | t.Errorf("Expected error readdir(nofound)") 178 | } 179 | 180 | } 181 | 182 | func TestRemove(t *testing.T) { 183 | fs := Create() 184 | err := fs.Mkdir("/tmp", 0777) 185 | if err != nil { 186 | t.Fatalf("Mkdir error: %s", err) 187 | } 188 | f, err := fs.OpenFile("/tmp/README.txt", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666) 189 | if err != nil { 190 | t.Fatalf("Create error: %s", err) 191 | } 192 | if _, err := f.Write([]byte("test")); err != nil { 193 | t.Fatalf("Write error: %s", err) 194 | } 195 | f.Close() 196 | 197 | // remove non existing file 198 | if err := fs.Remove("/nonexisting.txt"); err == nil { 199 | t.Errorf("Expected remove to fail") 200 | } 201 | 202 | // remove non existing file from an non existing directory 203 | if err := fs.Remove("/nonexisting/nonexisting.txt"); err == nil { 204 | t.Errorf("Expected remove to fail") 205 | } 206 | 207 | // remove created file 208 | err = fs.Remove(f.Name()) 209 | if err != nil { 210 | t.Errorf("Remove failed: %s", err) 211 | } 212 | 213 | if _, err = fs.OpenFile("/tmp/README.txt", os.O_RDWR, 0666); err == nil { 214 | t.Errorf("Could open removed file!") 215 | } 216 | 217 | err = fs.Remove("/tmp") 218 | if err != nil { 219 | t.Errorf("Remove failed: %s", err) 220 | } 221 | if fis, err := fs.ReadDir("/"); err != nil { 222 | t.Errorf("Readdir error: %s", err) 223 | } else if len(fis) != 0 { 224 | t.Errorf("Found files: %s", fis) 225 | } 226 | } 227 | 228 | func TestReadWrite(t *testing.T) { 229 | fs := Create() 230 | f, err := fs.OpenFile("/readme.txt", os.O_CREATE|os.O_RDWR, 0666) 231 | if err != nil { 232 | t.Fatalf("Could not open file: %s", err) 233 | } 234 | 235 | // Write first dots 236 | if n, err := f.Write([]byte(dots)); err != nil { 237 | t.Errorf("Unexpected error: %s", err) 238 | } else if n != len(dots) { 239 | t.Errorf("Invalid write count: %d", n) 240 | } 241 | 242 | // Write abc 243 | if n, err := f.Write([]byte(abc)); err != nil { 244 | t.Errorf("Unexpected error: %s", err) 245 | } else if n != len(abc) { 246 | t.Errorf("Invalid write count: %d", n) 247 | } 248 | 249 | // Seek to beginning of file 250 | if n, err := f.Seek(0, os.SEEK_SET); err != nil || n != 0 { 251 | t.Errorf("Seek error: %d %s", n, err) 252 | } 253 | 254 | // Seek to end of file 255 | if n, err := f.Seek(0, os.SEEK_END); err != nil || n != 32 { 256 | t.Errorf("Seek error: %d %s", n, err) 257 | } 258 | 259 | // Write dots at end of file 260 | if n, err := f.Write([]byte(dots)); err != nil { 261 | t.Errorf("Unexpected error: %s", err) 262 | } else if n != len(dots) { 263 | t.Errorf("Invalid write count: %d", n) 264 | } 265 | 266 | // Seek to beginning of file 267 | if n, err := f.Seek(0, os.SEEK_SET); err != nil || n != 0 { 268 | t.Errorf("Seek error: %d %s", n, err) 269 | } 270 | 271 | p := make([]byte, len(dots)+len(abc)+len(dots)) 272 | if n, err := f.Read(p); err != nil || n != len(dots)+len(abc)+len(dots) { 273 | t.Errorf("Read error: %d %s", n, err) 274 | } else if s := string(p); s != dots+abc+dots { 275 | t.Errorf("Invalid read: %s", s) 276 | } 277 | } 278 | 279 | func TestOpenRO(t *testing.T) { 280 | fs := Create() 281 | f, err := fs.OpenFile("/readme.txt", os.O_CREATE|os.O_RDONLY, 0666) 282 | if err != nil { 283 | t.Fatalf("Could not open file: %s", err) 284 | } 285 | 286 | // Write first dots 287 | if _, err := f.Write([]byte(dots)); err == nil { 288 | t.Fatalf("Expected write error") 289 | } 290 | f.Close() 291 | } 292 | 293 | func TestOpenWO(t *testing.T) { 294 | fs := Create() 295 | f, err := fs.OpenFile("/readme.txt", os.O_CREATE|os.O_WRONLY, 0666) 296 | if err != nil { 297 | t.Fatalf("Could not open file: %s", err) 298 | } 299 | 300 | // Write first dots 301 | if n, err := f.Write([]byte(dots)); err != nil { 302 | t.Errorf("Unexpected error: %s", err) 303 | } else if n != len(dots) { 304 | t.Errorf("Invalid write count: %d", n) 305 | } 306 | 307 | // Seek to beginning of file 308 | if n, err := f.Seek(0, os.SEEK_SET); err != nil || n != 0 { 309 | t.Errorf("Seek error: %d %s", n, err) 310 | } 311 | 312 | // Try reading 313 | p := make([]byte, len(dots)) 314 | if n, err := f.Read(p); err == nil || n > 0 { 315 | t.Errorf("Expected invalid read: %d %s", n, err) 316 | } 317 | 318 | f.Close() 319 | } 320 | 321 | func TestOpenAppend(t *testing.T) { 322 | fs := Create() 323 | f, err := fs.OpenFile("/readme.txt", os.O_CREATE|os.O_RDWR, 0666) 324 | if err != nil { 325 | t.Fatalf("Could not open file: %s", err) 326 | } 327 | 328 | // Write first dots 329 | if n, err := f.Write([]byte(dots)); err != nil { 330 | t.Errorf("Unexpected error: %s", err) 331 | } else if n != len(dots) { 332 | t.Errorf("Invalid write count: %d", n) 333 | } 334 | f.Close() 335 | 336 | // Reopen file in append mode 337 | f, err = fs.OpenFile("/readme.txt", os.O_APPEND|os.O_RDWR, 0666) 338 | if err != nil { 339 | t.Fatalf("Could not open file: %s", err) 340 | } 341 | 342 | // append dots 343 | if n, err := f.Write([]byte(abc)); err != nil { 344 | t.Errorf("Unexpected error: %s", err) 345 | } else if n != len(abc) { 346 | t.Errorf("Invalid write count: %d", n) 347 | } 348 | 349 | // Seek to beginning of file 350 | if n, err := f.Seek(0, os.SEEK_SET); err != nil || n != 0 { 351 | t.Errorf("Seek error: %d %s", n, err) 352 | } 353 | 354 | p := make([]byte, len(dots)+len(abc)) 355 | if n, err := f.Read(p); err != nil || n != len(dots)+len(abc) { 356 | t.Errorf("Read error: %d %s", n, err) 357 | } else if s := string(p); s != dots+abc { 358 | t.Errorf("Invalid read: %s", s) 359 | } 360 | f.Close() 361 | } 362 | 363 | func TestTruncateToLength(t *testing.T) { 364 | var params = []struct { 365 | size int64 366 | err bool 367 | }{ 368 | {-1, true}, 369 | {0, false}, 370 | {int64(len(dots) - 1), false}, 371 | {int64(len(dots)), false}, 372 | {int64(len(dots) + 1), false}, 373 | } 374 | for _, param := range params { 375 | fs := Create() 376 | f, err := fs.OpenFile("/readme.txt", os.O_CREATE|os.O_RDWR, 0666) 377 | if err != nil { 378 | t.Fatalf("Could not open file: %s", err) 379 | } 380 | if n, err := f.Write([]byte(dots)); err != nil { 381 | t.Errorf("Unexpected error: %s", err) 382 | } else if n != len(dots) { 383 | t.Errorf("Invalid write count: %d", n) 384 | } 385 | f.Close() 386 | 387 | newSize := param.size 388 | err = f.Truncate(newSize) 389 | if param.err { 390 | if err == nil { 391 | t.Errorf("Error expected truncating file to length %d", newSize) 392 | } 393 | return 394 | } else if err != nil { 395 | t.Errorf("Error truncating file: %s", err) 396 | } 397 | 398 | b, err := readFile(fs, "/readme.txt") 399 | if err != nil { 400 | t.Errorf("Error reading truncated file: %s", err) 401 | } 402 | if int64(len(b)) != newSize { 403 | t.Errorf("File should be empty after truncation: %d", len(b)) 404 | } 405 | if fi, err := fs.Stat("/readme.txt"); err != nil { 406 | t.Errorf("Error stat file: %s", err) 407 | } else if fi.Size() != newSize { 408 | t.Errorf("Filesize should be %d after truncation", newSize) 409 | } 410 | } 411 | } 412 | 413 | func TestTruncateToZero(t *testing.T) { 414 | const content = "read me" 415 | fs := Create() 416 | if _, err := writeFile(fs, "/readme.txt", os.O_CREATE|os.O_RDWR, 0666, []byte(content)); err != nil { 417 | t.Errorf("Unexpected error writing file: %s", err) 418 | } 419 | 420 | f, err := fs.OpenFile("/readme.txt", os.O_RDWR|os.O_TRUNC, 0666) 421 | if err != nil { 422 | t.Errorf("Error opening file truncated: %s", err) 423 | } 424 | f.Close() 425 | 426 | b, err := readFile(fs, "/readme.txt") 427 | if err != nil { 428 | t.Errorf("Error reading truncated file: %s", err) 429 | } 430 | if len(b) != 0 { 431 | t.Errorf("File should be empty after truncation") 432 | } 433 | if fi, err := fs.Stat("/readme.txt"); err != nil { 434 | t.Errorf("Error stat file: %s", err) 435 | } else if fi.Size() != 0 { 436 | t.Errorf("Filesize should be 0 after truncation") 437 | } 438 | } 439 | 440 | func TestStat(t *testing.T) { 441 | fs := Create() 442 | f, err := fs.OpenFile("/readme.txt", os.O_CREATE|os.O_RDWR, 0666) 443 | if err != nil { 444 | t.Fatalf("Could not open file: %s", err) 445 | } 446 | 447 | // Write first dots 448 | if n, err := f.Write([]byte(dots)); err != nil { 449 | t.Fatalf("Unexpected error: %s", err) 450 | } else if n != len(dots) { 451 | t.Fatalf("Invalid write count: %d", n) 452 | } 453 | f.Close() 454 | 455 | if err := fs.Mkdir("/tmp", 0777); err != nil { 456 | t.Fatalf("Mkdir error: %s", err) 457 | } 458 | 459 | fi, err := fs.Stat(f.Name()) 460 | if err != nil { 461 | t.Errorf("Stat error: %s", err) 462 | } 463 | 464 | // Fileinfo name is base name 465 | if name := fi.Name(); name != "readme.txt" { 466 | t.Errorf("Invalid fileinfo name: %s", name) 467 | } 468 | 469 | // File name is abs name 470 | if name := f.Name(); name != "/readme.txt" { 471 | t.Errorf("Invalid file name: %s", name) 472 | } 473 | 474 | if s := fi.Size(); s != int64(len(dots)) { 475 | t.Errorf("Invalid size: %d", s) 476 | } 477 | if fi.IsDir() { 478 | t.Errorf("Invalid IsDir") 479 | } 480 | if m := fi.Mode(); m != 0666 { 481 | t.Errorf("Invalid mode: %d", m) 482 | } 483 | } 484 | 485 | func writeFile(fs vfs.Filesystem, name string, flags int, mode os.FileMode, b []byte) (int, error) { 486 | f, err := fs.OpenFile(name, flags, mode) 487 | if err != nil { 488 | return 0, err 489 | } 490 | return f.Write(b) 491 | } 492 | 493 | func readFile(fs vfs.Filesystem, name string) ([]byte, error) { 494 | f, err := fs.OpenFile(name, os.O_RDONLY, 0) 495 | if err != nil { 496 | return nil, err 497 | } 498 | return ioutil.ReadAll(f) 499 | } 500 | 501 | func TestRename(t *testing.T) { 502 | const content = "read me" 503 | fs := Create() 504 | if _, err := writeFile(fs, "/readme.txt", os.O_CREATE|os.O_RDWR, 0666, []byte(content)); err != nil { 505 | t.Errorf("Unexpected error writing file: %s", err) 506 | } 507 | 508 | if err := fs.Rename("/readme.txt", "/README.txt"); err != nil { 509 | t.Errorf("Unexpected error renaming file: %s", err) 510 | } 511 | 512 | if _, err := fs.Stat("/readme.txt"); err == nil { 513 | t.Errorf("Old file still exists") 514 | } 515 | 516 | if _, err := fs.Stat("/README.txt"); err != nil { 517 | t.Errorf("Error stat newfile: %s", err) 518 | } 519 | if b, err := readFile(fs, "/README.txt"); err != nil { 520 | t.Errorf("Error reading file: %s", err) 521 | } else if s := string(b); s != content { 522 | t.Errorf("Invalid content: %s", s) 523 | } 524 | 525 | // Rename unknown file 526 | if err := fs.Rename("/nonexisting.txt", "/goodtarget.txt"); err == nil { 527 | t.Errorf("Expected error renaming file") 528 | } 529 | 530 | // Rename unknown file in nonexisting directory 531 | if err := fs.Rename("/nonexisting/nonexisting.txt", "/goodtarget.txt"); err == nil { 532 | t.Errorf("Expected error renaming file") 533 | } 534 | 535 | // Rename existing file to nonexisting directory 536 | if err := fs.Rename("/README.txt", "/nonexisting/nonexisting.txt"); err == nil { 537 | t.Errorf("Expected error renaming file") 538 | } 539 | 540 | if err := fs.Mkdir("/newdirectory", 0777); err != nil { 541 | t.Errorf("Error creating directory: %s", err) 542 | } 543 | 544 | if err := fs.Rename("/README.txt", "/newdirectory/README.txt"); err != nil { 545 | t.Errorf("Error renaming file: %s", err) 546 | } 547 | 548 | // Create the same file again at root 549 | if _, err := writeFile(fs, "/README.txt", os.O_CREATE|os.O_RDWR, 0666, []byte(content)); err != nil { 550 | t.Errorf("Unexpected error writing file: %s", err) 551 | } 552 | 553 | // Overwrite existing file 554 | if err := fs.Rename("/newdirectory/README.txt", "/README.txt"); err == nil { 555 | t.Errorf("Expected error renaming file") 556 | } 557 | 558 | } 559 | 560 | func TestModTime(t *testing.T) { 561 | fs := Create() 562 | 563 | tBeforeWrite := time.Now() 564 | writeFile(fs, "/readme.txt", os.O_CREATE|os.O_RDWR, 0666, []byte{0, 0, 0}) 565 | fi, _ := fs.Stat("/readme.txt") 566 | mtimeAfterWrite := fi.ModTime() 567 | 568 | if !mtimeAfterWrite.After(tBeforeWrite) { 569 | t.Error("Open should modify mtime") 570 | } 571 | 572 | f, err := fs.OpenFile("/readme.txt", os.O_RDONLY, 0666) 573 | if err != nil { 574 | t.Fatalf("Could not open file: %s", err) 575 | } 576 | f.Close() 577 | tAfterRead := fi.ModTime() 578 | 579 | if tAfterRead != mtimeAfterWrite { 580 | t.Error("Open with O_RDONLY should not modify mtime") 581 | } 582 | } 583 | -------------------------------------------------------------------------------- /mountfs/doc.go: -------------------------------------------------------------------------------- 1 | // Package mountfs defines a filesystem supporting 2 | // the composition of multiple filesystems by mountpoints. 3 | package mountfs 4 | -------------------------------------------------------------------------------- /mountfs/example_test.go: -------------------------------------------------------------------------------- 1 | package mountfs_test 2 | 3 | import ( 4 | "github.com/blang/vfs" 5 | "github.com/blang/vfs/memfs" 6 | "github.com/blang/vfs/mountfs" 7 | ) 8 | 9 | func ExampleMountFS() { 10 | // Create a vfs supporting mounts 11 | // The root fs is accessing the filesystem of the underlying OS 12 | fs := mountfs.Create(vfs.OS()) 13 | 14 | // Mount a memfs inside /memfs 15 | // /memfs may not exist 16 | fs.Mount(memfs.Create(), "/memfs") 17 | 18 | // This will create /testdir inside the memfs 19 | fs.Mkdir("/memfs/testdir", 0777) 20 | 21 | // This will create /tmp/testdir inside your OS fs 22 | fs.Mkdir("/tmp/testdir", 0777) 23 | } 24 | -------------------------------------------------------------------------------- /mountfs/mountfs.go: -------------------------------------------------------------------------------- 1 | package mountfs 2 | 3 | import ( 4 | "errors" 5 | "github.com/blang/vfs" 6 | "os" 7 | filepath "path" 8 | "strings" 9 | ) 10 | 11 | // ErrBoundary is returned if an operation 12 | // can not act across filesystem boundaries. 13 | var ErrBoundary = errors.New("Crossing boundary") 14 | 15 | // Create a new MountFS based on a root filesystem. 16 | func Create(rootFS vfs.Filesystem) *MountFS { 17 | return &MountFS{ 18 | rootFS: rootFS, 19 | mounts: make(map[string]vfs.Filesystem), 20 | parents: make(map[string][]string), 21 | } 22 | } 23 | 24 | // MountFS represents a filesystem build upon a root filesystem 25 | // and multiple filesystems can be mounted inside it. 26 | // In contrast to unix filesystems, the mount path may 27 | // not be a directory or event exist. 28 | // 29 | // Only filesystems with the same path separator are compatible. 30 | // It's not possible to mount a specific source directory, only the 31 | // root of the filesystem can be mounted, use a chroot in this case. 32 | // The resulting filesystem is case-sensitive. 33 | type MountFS struct { 34 | rootFS vfs.Filesystem 35 | mounts map[string]vfs.Filesystem 36 | parents map[string][]string 37 | } 38 | 39 | // Mount mounts a filesystem on the given path. 40 | // Mounts inside mounts are supported, the longest path match will be taken. 41 | // Mount paths may be overwritten if set on the same path. 42 | // Path `/` can be used to change rootfs. 43 | // Only absolute paths are allowed. 44 | func (fs *MountFS) Mount(mount vfs.Filesystem, path string) error { 45 | pathSeparator := string(fs.rootFS.PathSeparator()) 46 | 47 | // Clean path and make absolute 48 | path = filepath.Clean(path) 49 | segm := vfs.SplitPath(path, pathSeparator) 50 | segm[0] = "" // make absolute 51 | path = strings.Join(segm, pathSeparator) 52 | 53 | // Change rootfs disabled 54 | if path == "" { 55 | fs.rootFS = mount 56 | return nil 57 | } 58 | 59 | parent := strings.Join(segm[0:len(segm)-1], pathSeparator) 60 | if parent == "" { 61 | parent = "/" 62 | } 63 | fs.parents[parent] = append(fs.parents[parent], path) 64 | fs.mounts[path] = mount 65 | return nil 66 | } 67 | 68 | // PathSeparator returns the path separator 69 | func (fs MountFS) PathSeparator() uint8 { 70 | return fs.rootFS.PathSeparator() 71 | } 72 | 73 | // findMount finds a valid mountpoint for the given path. 74 | // It returns the corresponding filesystem and the path inside of this filesystem. 75 | func findMount(path string, mounts map[string]vfs.Filesystem, fallback vfs.Filesystem, pathSeparator string) (vfs.Filesystem, string) { 76 | path = filepath.Clean(path) 77 | segs := vfs.SplitPath(path, pathSeparator) 78 | l := len(segs) 79 | for i := l; i > 0; i-- { 80 | mountPath := strings.Join(segs[0:i], pathSeparator) 81 | if fs, ok := mounts[mountPath]; ok { 82 | return fs, "/" + strings.Join(segs[i:l], pathSeparator) 83 | } 84 | } 85 | return fallback, path 86 | } 87 | 88 | type innerFile struct { 89 | vfs.File 90 | name string 91 | } 92 | 93 | // Name returns the full path inside mountfs 94 | func (f innerFile) Name() string { 95 | return f.name 96 | } 97 | 98 | // OpenFile find the mount of the given path and executes OpenFile 99 | // on the corresponding filesystem. 100 | // It wraps the resulting file to return the path inside mountfs on Name() 101 | func (fs MountFS) OpenFile(name string, flag int, perm os.FileMode) (vfs.File, error) { 102 | mount, innerPath := findMount(name, fs.mounts, fs.rootFS, string(fs.PathSeparator())) 103 | file, err := mount.OpenFile(innerPath, flag, perm) 104 | return innerFile{File: file, name: name}, err 105 | } 106 | 107 | // Remove removes a file or directory 108 | func (fs MountFS) Remove(name string) error { 109 | mount, innerPath := findMount(name, fs.mounts, fs.rootFS, string(fs.PathSeparator())) 110 | return mount.Remove(innerPath) 111 | } 112 | 113 | // Rename renames a file. 114 | // Renames across filesystems are not allowed. 115 | func (fs MountFS) Rename(oldpath, newpath string) error { 116 | oldMount, oldInnerPath := findMount(oldpath, fs.mounts, fs.rootFS, string(fs.PathSeparator())) 117 | newMount, newInnerPath := findMount(newpath, fs.mounts, fs.rootFS, string(fs.PathSeparator())) 118 | if oldMount != newMount { 119 | return ErrBoundary 120 | } 121 | return oldMount.Rename(oldInnerPath, newInnerPath) 122 | } 123 | 124 | // Mkdir creates a directory 125 | func (fs MountFS) Mkdir(name string, perm os.FileMode) error { 126 | mount, innerPath := findMount(name, fs.mounts, fs.rootFS, string(fs.PathSeparator())) 127 | return mount.Mkdir(innerPath, perm) 128 | } 129 | 130 | type innerFileInfo struct { 131 | os.FileInfo 132 | name string 133 | } 134 | 135 | func (fi innerFileInfo) Name() string { 136 | return fi.name 137 | } 138 | 139 | // Stat returns the fileinfo of a file 140 | func (fs MountFS) Stat(name string) (os.FileInfo, error) { 141 | mount, innerPath := findMount(name, fs.mounts, fs.rootFS, string(fs.PathSeparator())) 142 | fi, err := mount.Stat(innerPath) 143 | if innerPath == "/" { 144 | return innerFileInfo{FileInfo: fi, name: filepath.Base(name)}, err 145 | } 146 | return fi, err 147 | } 148 | 149 | // Lstat returns the fileinfo of a file or link. 150 | func (fs MountFS) Lstat(name string) (os.FileInfo, error) { 151 | mount, innerPath := findMount(name, fs.mounts, fs.rootFS, string(fs.PathSeparator())) 152 | fi, err := mount.Lstat(innerPath) 153 | if innerPath == "/" { 154 | return innerFileInfo{FileInfo: fi, name: filepath.Base(name)}, err 155 | } 156 | return fi, err 157 | } 158 | 159 | // ReadDir reads the directory named by path and returns a list of sorted directory entries. 160 | func (fs MountFS) ReadDir(path string) ([]os.FileInfo, error) { 161 | path = filepath.Clean(path) 162 | mount, innerPath := findMount(path, fs.mounts, fs.rootFS, string(fs.PathSeparator())) 163 | 164 | fis, err := mount.ReadDir(innerPath) 165 | if err != nil { 166 | return fis, err 167 | } 168 | 169 | // Add mountpoints 170 | if childs, ok := fs.parents[path]; ok { 171 | for _, c := range childs { 172 | mfi, err := fs.Stat(c) 173 | if err == nil { 174 | fis = append(fis, mfi) 175 | } 176 | } 177 | } 178 | return fis, err 179 | } 180 | -------------------------------------------------------------------------------- /mountfs/mountfs_test.go: -------------------------------------------------------------------------------- 1 | package mountfs 2 | 3 | import ( 4 | "errors" 5 | "github.com/blang/vfs" 6 | "os" 7 | "testing" 8 | ) 9 | 10 | type mountTest struct { 11 | mounts []string 12 | results map[string]mtres 13 | } 14 | 15 | type mtres struct { 16 | mountPath string 17 | innerPath string 18 | } 19 | 20 | var mountTests = []mountTest{ 21 | { 22 | []string{ 23 | "/tmp", 24 | }, 25 | map[string]mtres{ 26 | "/": {"/", "/"}, 27 | "/tmp": {"/tmp", "/"}, 28 | "/tmp/test": {"/tmp", "/test"}, 29 | }, 30 | }, 31 | 32 | { 33 | []string{ 34 | "/home", 35 | "/home/user1", 36 | "/home/user2", 37 | }, 38 | map[string]mtres{ 39 | "/": {"/", "/"}, 40 | "/tmp": {"/", "/tmp"}, 41 | "/tmp/test": {"/", "/tmp/test"}, 42 | "/home": {"/home", "/"}, 43 | "/home/user1": {"/home/user1", "/"}, 44 | "/home/user2": {"/home/user2", "/"}, 45 | "/home/user1/subdir": {"/home/user1", "/subdir"}, 46 | "/home/user2/subdir/subsubdir": {"/home/user2", "/subdir/subsubdir"}, 47 | }, 48 | }, 49 | } 50 | 51 | func TestInterface(t *testing.T) { 52 | _ = vfs.Filesystem(Create(nil)) 53 | } 54 | 55 | func TestFindMount(t *testing.T) { 56 | for i, mtest := range mountTests { 57 | mounts := make(map[string]vfs.Filesystem) 58 | revmounts := make(map[vfs.Filesystem]string) 59 | for _, mount := range mtest.mounts { 60 | fs := vfs.Dummy(nil) 61 | mounts[mount] = fs 62 | revmounts[fs] = mount 63 | } 64 | fallback := vfs.Dummy(nil) 65 | for path, expRes := range mtest.results { 66 | expMountPath := expRes.mountPath 67 | expInnerPath := expRes.innerPath 68 | 69 | res, resInnerPath := findMount(path, mounts, fallback, "/") 70 | if res == nil { 71 | t.Errorf("Got nil") 72 | continue 73 | } 74 | if res == fallback { 75 | if expMountPath != "/" { 76 | t.Fatalf("Invalid mount result test case %d, mounts: %q, path: %q, expected: %q, got: %q", i, mtest.mounts, path, expMountPath, "/") 77 | } 78 | continue 79 | } 80 | 81 | if resMountPath, ok := revmounts[res]; ok { 82 | if resMountPath != expMountPath { 83 | t.Fatalf("Invalid mount, test case %d, mounts: %q, path: %q, expected: %q, got: %q", i, mtest.mounts, path, expMountPath, resMountPath) 84 | } 85 | if resInnerPath != expInnerPath { 86 | t.Fatalf("Invalid inner path, test case %d, mounts: %q, path: %q, expected: %q, got: %q", i, mtest.mounts, path, expInnerPath, resInnerPath) 87 | } 88 | continue 89 | } 90 | t.Fatalf("Invalid mount result test case %d, mounts: %q, path: %q, expected: %q, got invalid result", i, mtest.mounts, path, expMountPath) 91 | } 92 | } 93 | } 94 | 95 | type testDummyFS struct { 96 | vfs.Filesystem 97 | lastPath string 98 | lastPath2 string 99 | } 100 | 101 | func (fs testDummyFS) OpenFile(name string, flag int, perm os.FileMode) (vfs.File, error) { 102 | return vfs.DummyFile(errors.New("Mount")), nil 103 | } 104 | 105 | func TestCreate(t *testing.T) { 106 | errRoot := errors.New("Rootfs") 107 | errMount := errors.New("Mount") 108 | rootFS := vfs.Dummy(errRoot) 109 | mountFS := vfs.Dummy(errMount) 110 | fs := Create(rootFS) 111 | if err := fs.Mkdir("/dir", 0); err != errRoot { 112 | t.Errorf("Expected error from rootFS: %s", err) 113 | } 114 | 115 | fs.Mount(mountFS, "/tmp") 116 | if err := fs.Mkdir("/tmp/dir", 0); err != errMount { 117 | t.Errorf("Expected error from mountFS: %s", err) 118 | } 119 | 120 | // Change rootfs 121 | fs.Mount(mountFS, "/") 122 | if err := fs.Mkdir("/dir2", 0); err != errMount { 123 | t.Errorf("Expected error from mountFS: %s", err) 124 | } 125 | } 126 | 127 | func TestOpenFile(t *testing.T) { 128 | errRoot := errors.New("Rootfs") 129 | errMount := errors.New("Mount") 130 | rootFS := vfs.Dummy(errRoot) 131 | mountFS := &testDummyFS{Filesystem: vfs.Dummy(errMount)} 132 | fs := Create(rootFS) 133 | fs.Mount(mountFS, "/tmp") 134 | 135 | // Test selection of correct fs 136 | f, err := fs.OpenFile("/tmp/testfile", os.O_CREATE, 0) 137 | if err != nil { 138 | t.Errorf("Unexpected error: %s", err) 139 | } 140 | if n := f.Name(); n != "/tmp/testfile" { 141 | t.Errorf("Unexpected filename: %s", n) 142 | } 143 | } 144 | 145 | func (fs *testDummyFS) Mkdir(name string, perm os.FileMode) error { 146 | fs.lastPath = name 147 | return fs.Filesystem.Mkdir(name, perm) 148 | } 149 | 150 | func TestMkdir(t *testing.T) { 151 | errRoot := errors.New("Rootfs") 152 | errMount := errors.New("Mount") 153 | rootFS := vfs.Dummy(errRoot) 154 | mountFS := &testDummyFS{Filesystem: vfs.Dummy(errMount)} 155 | fs := Create(rootFS) 156 | fs.Mount(mountFS, "/tmp") 157 | 158 | // Test selection of correct fs 159 | err := fs.Mkdir("/tmp/testdir", 0) 160 | if err != errMount { 161 | t.Errorf("Wrong filesystem selected: %s", err) 162 | } 163 | if n := mountFS.lastPath; n != "/testdir" { 164 | t.Errorf("Incorrect inner name: %s", n) 165 | } 166 | } 167 | 168 | func (fs *testDummyFS) Remove(name string) error { 169 | fs.lastPath = name 170 | return fs.Filesystem.Remove(name) 171 | } 172 | 173 | func TestRemove(t *testing.T) { 174 | errRoot := errors.New("Rootfs") 175 | errMount := errors.New("Mount") 176 | rootFS := vfs.Dummy(errRoot) 177 | mountFS := &testDummyFS{Filesystem: vfs.Dummy(errMount)} 178 | fs := Create(rootFS) 179 | fs.Mount(mountFS, "/tmp") 180 | 181 | // Test selection of correct fs 182 | err := fs.Remove("/tmp/testdir") 183 | if err != errMount { 184 | t.Errorf("Wrong filesystem selected: %s", err) 185 | } 186 | if n := mountFS.lastPath; n != "/testdir" { 187 | t.Errorf("Incorrect inner name: %s", n) 188 | } 189 | } 190 | 191 | func (fs *testDummyFS) Rename(oldpath, newpath string) error { 192 | fs.lastPath = oldpath 193 | fs.lastPath2 = newpath 194 | return fs.Filesystem.Rename(oldpath, newpath) 195 | } 196 | 197 | func TestRename(t *testing.T) { 198 | errRoot := errors.New("Rootfs") 199 | errMount := errors.New("Mount") 200 | rootFS := vfs.Dummy(errRoot) 201 | mountFS := &testDummyFS{Filesystem: vfs.Dummy(errMount)} 202 | fs := Create(rootFS) 203 | fs.Mount(mountFS, "/tmp") 204 | 205 | // Test selection of correct fs 206 | err := fs.Rename("/tmp/testfile1", "/tmp/testfile2") 207 | if err != errMount { 208 | t.Errorf("Wrong filesystem selected: %s", err) 209 | } 210 | if n := mountFS.lastPath; n != "/testfile1" { 211 | t.Errorf("Incorrect inner name (oldpath): %s", n) 212 | } 213 | if n := mountFS.lastPath2; n != "/testfile2" { 214 | t.Errorf("Incorrect inner name (newpath): %s", n) 215 | } 216 | } 217 | 218 | func TestRenameBoundaries(t *testing.T) { 219 | errRoot := errors.New("Rootfs") 220 | errMount := errors.New("Mount") 221 | rootFS := vfs.Dummy(errRoot) 222 | mountFS := vfs.Dummy(errMount) 223 | fs := Create(rootFS) 224 | fs.Mount(mountFS, "/tmp") 225 | 226 | // Test selection of correct fs 227 | err := fs.Rename("/tmp/testfile1", "/testfile2") 228 | if err != ErrBoundary { 229 | t.Errorf("Invalid error, should return boundaries error: %s", err) 230 | } 231 | } 232 | 233 | func (fs *testDummyFS) Stat(name string) (os.FileInfo, error) { 234 | fs.lastPath = name 235 | return vfs.DumFileInfo{ 236 | IName: name, 237 | }, nil 238 | } 239 | 240 | func TestStat(t *testing.T) { 241 | errRoot := errors.New("Rootfs") 242 | errMount := errors.New("Mount") 243 | rootFS := vfs.Dummy(errRoot) 244 | mountFS := &testDummyFS{Filesystem: vfs.Dummy(errMount)} 245 | fs := Create(rootFS) 246 | fs.Mount(mountFS, "/tmp") 247 | 248 | // Test selection of correct fs 249 | _, err := fs.Stat("/tmp/testfile1") 250 | if err != nil { 251 | t.Errorf("Wrong filesystem selected: %s", err) 252 | } 253 | 254 | if n := mountFS.lastPath; n != "/testfile1" { 255 | t.Errorf("Incorrect inner name: %s", n) 256 | } 257 | } 258 | 259 | func TestStatMountPoint(t *testing.T) { 260 | errRoot := errors.New("Rootfs") 261 | errMount := errors.New("Mount") 262 | rootFS := vfs.Dummy(errRoot) 263 | mountFS := &testDummyFS{Filesystem: vfs.Dummy(errMount)} 264 | fs := Create(rootFS) 265 | fs.Mount(mountFS, "/tmp") 266 | 267 | // Test selection of correct fs 268 | fi, err := fs.Stat("/tmp") 269 | if err != nil { 270 | t.Errorf("Wrong filesystem selected: %s", err) 271 | } 272 | 273 | if n := mountFS.lastPath; n != "/" { 274 | t.Errorf("Incorrect inner name: %s", n) 275 | } 276 | 277 | if n := fi.Name(); n != "tmp" { 278 | t.Errorf("Mountpoint should be return correct name, got instead: %s", n) 279 | } 280 | } 281 | 282 | func (fs *testDummyFS) Lstat(name string) (os.FileInfo, error) { 283 | fs.lastPath = name 284 | return vfs.DumFileInfo{ 285 | IName: name, 286 | }, nil 287 | } 288 | 289 | func TestLstat(t *testing.T) { 290 | errRoot := errors.New("Rootfs") 291 | errMount := errors.New("Mount") 292 | rootFS := vfs.Dummy(errRoot) 293 | mountFS := &testDummyFS{Filesystem: vfs.Dummy(errMount)} 294 | fs := Create(rootFS) 295 | fs.Mount(mountFS, "/tmp") 296 | 297 | // Test selection of correct fs 298 | _, err := fs.Lstat("/tmp/testfile1") 299 | if err != nil { 300 | t.Errorf("Wrong filesystem selected: %s", err) 301 | } 302 | 303 | if n := mountFS.lastPath; n != "/testfile1" { 304 | t.Errorf("Incorrect inner name: %s", n) 305 | } 306 | } 307 | 308 | func TestLstatMountPoint(t *testing.T) { 309 | errRoot := errors.New("Rootfs") 310 | errMount := errors.New("Mount") 311 | rootFS := vfs.Dummy(errRoot) 312 | mountFS := &testDummyFS{Filesystem: vfs.Dummy(errMount)} 313 | fs := Create(rootFS) 314 | fs.Mount(mountFS, "/tmp") 315 | 316 | // Test selection of correct fs 317 | fi, err := fs.Lstat("/tmp") 318 | if err != nil { 319 | t.Errorf("Wrong filesystem selected: %s", err) 320 | } 321 | 322 | if n := mountFS.lastPath; n != "/" { 323 | t.Errorf("Incorrect inner name: %s", n) 324 | } 325 | 326 | if n := fi.Name(); n != "tmp" { 327 | t.Errorf("Mountpoint should be return correct name, got instead: %s", n) 328 | } 329 | } 330 | 331 | func (fs *testDummyFS) ReadDir(path string) ([]os.FileInfo, error) { 332 | fs.lastPath = path 333 | return []os.FileInfo{ 334 | vfs.DumFileInfo{ 335 | IName: "testcontent", 336 | }, 337 | }, nil 338 | } 339 | 340 | func TestReadDir(t *testing.T) { 341 | errRoot := errors.New("Rootfs") 342 | errMount := errors.New("Mount") 343 | rootFS := vfs.Dummy(errRoot) 344 | mountFS := &testDummyFS{Filesystem: vfs.Dummy(errMount)} 345 | fs := Create(rootFS) 346 | fs.Mount(mountFS, "/tmp") 347 | 348 | // Test selection of correct fs 349 | fis, err := fs.ReadDir("/tmp") 350 | if err != nil { 351 | t.Errorf("Wrong filesystem selected: %s", err) 352 | } 353 | 354 | if n := mountFS.lastPath; n != "/" { 355 | t.Errorf("Incorrect inner name: %s", n) 356 | } 357 | 358 | if len(fis) != 1 || fis[0].Name() != "testcontent" { 359 | t.Errorf("Expected fake file, but got: %s", fis) 360 | } 361 | } 362 | 363 | func TestReadDirMountPoints(t *testing.T) { 364 | errRoot := errors.New("Rootfs") 365 | errMount := errors.New("Mount") 366 | rootFS := &testDummyFS{Filesystem: vfs.Dummy(errRoot)} 367 | mountFS := &testDummyFS{Filesystem: vfs.Dummy(errMount)} 368 | fs := Create(rootFS) 369 | fs.Mount(mountFS, "/tmp") 370 | 371 | // Test selection of correct fs 372 | fis, err := fs.ReadDir("/") 373 | if err != nil { 374 | t.Errorf("Wrong filesystem selected: %s", err) 375 | } 376 | 377 | if n := rootFS.lastPath; n != "/" { 378 | t.Errorf("Incorrect inner name: %s", n) 379 | } 380 | 381 | if l := len(fis); l != 2 { 382 | t.Fatalf("Expected 2 files, one fake, one mountpoint: %d, %s", l, fis) 383 | } 384 | if n := fis[0].Name(); n != "testcontent" { 385 | t.Errorf("Expected fake file, but got: %s", fis) 386 | } 387 | if n := fis[1].Name(); n != "tmp" { 388 | t.Errorf("Expected mountpoint, but got: %s", fis) 389 | } 390 | } 391 | -------------------------------------------------------------------------------- /os.go: -------------------------------------------------------------------------------- 1 | package vfs 2 | 3 | import ( 4 | "io/ioutil" 5 | "os" 6 | ) 7 | 8 | // OsFS represents a filesystem backed by the filesystem of the underlying OS. 9 | type OsFS struct{} 10 | 11 | // OS returns a filesystem backed by the filesystem of the os. It wraps os.* stdlib operations. 12 | func OS() *OsFS { 13 | return &OsFS{} 14 | } 15 | 16 | // PathSeparator returns the path separator 17 | func (fs OsFS) PathSeparator() uint8 { 18 | return os.PathSeparator 19 | } 20 | 21 | // OpenFile wraps os.OpenFile 22 | func (fs OsFS) OpenFile(name string, flag int, perm os.FileMode) (File, error) { 23 | return os.OpenFile(name, flag, perm) 24 | } 25 | 26 | // Remove wraps os.Remove 27 | func (fs OsFS) Remove(name string) error { 28 | return os.Remove(name) 29 | } 30 | 31 | // Mkdir wraps os.Mkdir 32 | func (fs OsFS) Mkdir(name string, perm os.FileMode) error { 33 | return os.Mkdir(name, perm) 34 | } 35 | 36 | // Rename wraps os.Rename 37 | func (fs OsFS) Rename(oldpath, newpath string) error { 38 | return os.Rename(oldpath, newpath) 39 | } 40 | 41 | // Stat wraps os.Stat 42 | func (fs OsFS) Stat(name string) (os.FileInfo, error) { 43 | return os.Stat(name) 44 | } 45 | 46 | // Lstat wraps os.Lstat 47 | func (fs OsFS) Lstat(name string) (os.FileInfo, error) { 48 | return os.Lstat(name) 49 | } 50 | 51 | // ReadDir wraps ioutil.ReadDir 52 | func (fs OsFS) ReadDir(path string) ([]os.FileInfo, error) { 53 | return ioutil.ReadDir(path) 54 | } 55 | -------------------------------------------------------------------------------- /os_test.go: -------------------------------------------------------------------------------- 1 | package vfs 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | ) 7 | 8 | func TestOSInterface(t *testing.T) { 9 | _ = Filesystem(OS()) 10 | } 11 | 12 | func TestOSCreate(t *testing.T) { 13 | fs := OS() 14 | 15 | f, err := fs.OpenFile("/tmp/test123", os.O_CREATE|os.O_RDWR, 0666) 16 | if err != nil { 17 | t.Errorf("Create: %s", err) 18 | } 19 | err = f.Close() 20 | if err != nil { 21 | t.Errorf("Close: %s", err) 22 | } 23 | err = fs.Remove(f.Name()) 24 | if err != nil { 25 | t.Errorf("Remove: %s", err) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /path.go: -------------------------------------------------------------------------------- 1 | package vfs 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | // SplitPath splits the given path in segments: 8 | // "/" -> []string{""} 9 | // "./file" -> []string{".", "file"} 10 | // "file" -> []string{".", "file"} 11 | // "/usr/src/linux/" -> []string{"", "usr", "src", "linux"} 12 | // The returned slice of path segments consists of one more more segments. 13 | func SplitPath(path string, sep string) []string { 14 | path = strings.TrimSpace(path) 15 | path = strings.TrimSuffix(path, sep) 16 | if path == "" { // was "/" 17 | return []string{""} 18 | } 19 | if path == "." { 20 | return []string{"."} 21 | } 22 | 23 | if len(path) > 0 && !strings.HasPrefix(path, sep) && !strings.HasPrefix(path, "."+sep) { 24 | path = "./" + path 25 | } 26 | parts := strings.Split(path, sep) 27 | 28 | return parts 29 | } 30 | -------------------------------------------------------------------------------- /path_test.go: -------------------------------------------------------------------------------- 1 | package vfs 2 | 3 | import ( 4 | "reflect" 5 | 6 | "testing" 7 | ) 8 | 9 | func TestSplitPath(t *testing.T) { 10 | const PathSeperator = "/" 11 | if p := SplitPath("/", PathSeperator); !reflect.DeepEqual(p, []string{""}) { 12 | t.Errorf("Invalid path: %q", p) 13 | } 14 | if p := SplitPath("./test", PathSeperator); !reflect.DeepEqual(p, []string{".", "test"}) { 15 | t.Errorf("Invalid path: %q", p) 16 | } 17 | if p := SplitPath(".", PathSeperator); !reflect.DeepEqual(p, []string{"."}) { 18 | t.Errorf("Invalid path: %q", p) 19 | } 20 | if p := SplitPath("test", PathSeperator); !reflect.DeepEqual(p, []string{".", "test"}) { 21 | t.Errorf("Invalid path: %q", p) 22 | } 23 | if p := SplitPath("/usr/src/linux/", PathSeperator); !reflect.DeepEqual(p, []string{"", "usr", "src", "linux"}) { 24 | t.Errorf("Invalid path: %q", p) 25 | } 26 | if p := SplitPath("usr/src/linux/", PathSeperator); !reflect.DeepEqual(p, []string{".", "usr", "src", "linux"}) { 27 | t.Errorf("Invalid path: %q", p) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /prefixfs/prefixfs.go: -------------------------------------------------------------------------------- 1 | package prefixfs 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/blang/vfs" 7 | ) 8 | 9 | // A FS that prefixes the path in each vfs.Filesystem operation. 10 | type FS struct { 11 | vfs.Filesystem 12 | 13 | // Prefix is used to prefix the path in each vfs.Filesystem operation. 14 | Prefix string 15 | } 16 | 17 | // Create returns a file system that prefixes all paths and forwards to root. 18 | func Create(root vfs.Filesystem, prefix string) *FS { 19 | return &FS{root, prefix} 20 | } 21 | 22 | // PrefixPath returns path with the prefix prefixed. 23 | func (fs *FS) PrefixPath(path string) string { 24 | return fs.Prefix + string(fs.PathSeparator()) + path 25 | } 26 | 27 | // PathSeparator implements vfs.Filesystem. 28 | func (fs *FS) PathSeparator() uint8 { return fs.Filesystem.PathSeparator() } 29 | 30 | // OpenFile implements vfs.Filesystem. 31 | func (fs *FS) OpenFile(name string, flag int, perm os.FileMode) (vfs.File, error) { 32 | return fs.Filesystem.OpenFile(fs.PrefixPath(name), flag, perm) 33 | } 34 | 35 | // Remove implements vfs.Filesystem. 36 | func (fs *FS) Remove(name string) error { 37 | return fs.Filesystem.Remove(fs.PrefixPath(name)) 38 | } 39 | 40 | // Rename implements vfs.Filesystem. 41 | func (fs *FS) Rename(oldpath, newpath string) error { 42 | return fs.Filesystem.Rename(fs.PrefixPath(oldpath), fs.PrefixPath(newpath)) 43 | } 44 | 45 | // Mkdir implements vfs.Filesystem. 46 | func (fs *FS) Mkdir(name string, perm os.FileMode) error { 47 | return fs.Filesystem.Mkdir(fs.PrefixPath(name), perm) 48 | } 49 | 50 | // Stat implements vfs.Filesystem. 51 | func (fs *FS) Stat(name string) (os.FileInfo, error) { 52 | return fs.Filesystem.Stat(fs.PrefixPath(name)) 53 | } 54 | 55 | // Lstat implements vfs.Filesystem. 56 | func (fs *FS) Lstat(name string) (os.FileInfo, error) { 57 | return fs.Filesystem.Lstat(fs.PrefixPath(name)) 58 | } 59 | 60 | // ReadDir implements vfs.Filesystem. 61 | func (fs *FS) ReadDir(path string) ([]os.FileInfo, error) { 62 | return fs.Filesystem.ReadDir(fs.PrefixPath(path)) 63 | } 64 | -------------------------------------------------------------------------------- /prefixfs/prefixfs_test.go: -------------------------------------------------------------------------------- 1 | package prefixfs 2 | 3 | import ( 4 | "os" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/blang/vfs" 9 | "github.com/blang/vfs/memfs" 10 | ) 11 | 12 | const prefixPath = "/prefix" 13 | 14 | func prefix(path string) string { 15 | return prefixPath + "/" + path 16 | } 17 | 18 | func rootfs() vfs.Filesystem { 19 | rfs := memfs.Create() 20 | rfs.Mkdir(prefixPath, 0777) 21 | return rfs 22 | } 23 | 24 | func TestPathSeparator(t *testing.T) { 25 | rfs := rootfs() 26 | fs := Create(rfs, prefixPath) 27 | 28 | if fs.PathSeparator() != rfs.PathSeparator() { 29 | t.Errorf("fs.PathSeparator() != %v", rfs.PathSeparator()) 30 | } 31 | } 32 | 33 | func TestOpenFile(t *testing.T) { 34 | rfs := rootfs() 35 | fs := Create(rfs, prefixPath) 36 | 37 | f, err := fs.OpenFile("file", os.O_CREATE, 0666) 38 | defer f.Close() 39 | if err != nil { 40 | t.Errorf("OpenFile: %v", err) 41 | } 42 | 43 | _, err = rfs.Stat(prefix("file")) 44 | if os.IsNotExist(err) { 45 | t.Errorf("root:%v not found (%v)", prefix("file"), err) 46 | } 47 | } 48 | 49 | func TestRemove(t *testing.T) { 50 | rfs := rootfs() 51 | fs := Create(rfs, prefixPath) 52 | 53 | f, err := fs.OpenFile("file", os.O_CREATE, 0666) 54 | defer f.Close() 55 | if err != nil { 56 | t.Errorf("OpenFile: %v", err) 57 | } 58 | 59 | err = fs.Remove("file") 60 | if err != nil { 61 | t.Errorf("Remove: %v", err) 62 | } 63 | 64 | _, err = rfs.Stat(prefix("file")) 65 | if os.IsExist(err) { 66 | t.Errorf("root:%v found (%v)", prefix("file"), err) 67 | } 68 | } 69 | 70 | func TestRename(t *testing.T) { 71 | rfs := rootfs() 72 | fs := Create(rfs, prefixPath) 73 | 74 | f, err := fs.OpenFile("file", os.O_CREATE, 0666) 75 | defer f.Close() 76 | if err != nil { 77 | t.Errorf("OpenFile: %v", err) 78 | } 79 | 80 | err = fs.Rename("file", "file2") 81 | if err != nil { 82 | t.Errorf("Rename: %v", err) 83 | } 84 | 85 | _, err = rfs.Stat(prefix("file2")) 86 | if os.IsNotExist(err) { 87 | t.Errorf("root:%v not found (%v)", prefix("file2"), err) 88 | } 89 | } 90 | 91 | func TestMkdir(t *testing.T) { 92 | rfs := rootfs() 93 | fs := Create(rfs, prefixPath) 94 | 95 | err := fs.Mkdir("dir", 0777) 96 | if err != nil { 97 | t.Errorf("Mkdir: %v", err) 98 | } 99 | 100 | _, err = rfs.Stat(prefix("dir")) 101 | if os.IsNotExist(err) { 102 | t.Errorf("root:%v not found (%v)", prefix("dir"), err) 103 | } 104 | } 105 | 106 | func TestStat(t *testing.T) { 107 | rfs := rootfs() 108 | fs := Create(rfs, prefixPath) 109 | 110 | f, err := fs.OpenFile("file", os.O_CREATE, 0666) 111 | defer f.Close() 112 | if err != nil { 113 | t.Errorf("OpenFile: %v", err) 114 | } 115 | 116 | fi, err := fs.Stat("file") 117 | if os.IsNotExist(err) { 118 | t.Errorf("fs.Stat: %v", err) 119 | } 120 | 121 | rfi, err := rfs.Stat(prefix("file")) 122 | if os.IsNotExist(err) { 123 | t.Errorf("rfs.Stat: %v", err) 124 | } 125 | 126 | if fi.Name() != rfi.Name() { 127 | t.Errorf("FileInfo: Name not equal (fs:%v != root:%v)", fi.Name(), rfi.Name()) 128 | } 129 | 130 | if fi.Size() != rfi.Size() { 131 | t.Errorf("FileInfo: Size not equal (fs:%v != root:%v)", fi.Size(), rfi.Size()) 132 | } 133 | 134 | if fi.Mode() != rfi.Mode() { 135 | t.Errorf("FileInfo: Mode not equal (fs:%v != root:%v)", fi.Mode(), rfi.Mode()) 136 | } 137 | 138 | if fi.ModTime() != rfi.ModTime() { 139 | t.Errorf("FileInfo: ModTime not equal (fs:%v != root:%v)", fi.ModTime(), rfi.ModTime()) 140 | } 141 | 142 | if fi.IsDir() != rfi.IsDir() { 143 | t.Errorf("FileInfo: Mode not equal (fs:%v != root:%v)", fi.IsDir(), rfi.IsDir()) 144 | } 145 | 146 | if fi.Sys() != rfi.Sys() { 147 | t.Errorf("FileInfo: Sys not equal (fs:%v != root:%v)", fi.Sys(), rfi.Sys()) 148 | } 149 | } 150 | 151 | func TestLstat(t *testing.T) { 152 | rfs := rootfs() 153 | fs := Create(rfs, prefixPath) 154 | 155 | f, err := fs.OpenFile("file", os.O_CREATE, 0666) 156 | defer f.Close() 157 | if err != nil { 158 | t.Errorf("OpenFile: %v", err) 159 | } 160 | 161 | fi, err := fs.Lstat("file") 162 | if os.IsNotExist(err) { 163 | t.Errorf("fs.Lstat: %v", err) 164 | } 165 | 166 | rfi, err := rfs.Lstat(prefix("file")) 167 | if os.IsNotExist(err) { 168 | t.Errorf("rfs.Lstat: %v", err) 169 | } 170 | 171 | if fi.Name() != rfi.Name() { 172 | t.Errorf("FileInfo: Name not equal (fs:%v != root:%v)", fi.Name(), rfi.Name()) 173 | } 174 | 175 | if fi.Size() != rfi.Size() { 176 | t.Errorf("FileInfo: Size not equal (fs:%v != root:%v)", fi.Size(), rfi.Size()) 177 | } 178 | 179 | if fi.Mode() != rfi.Mode() { 180 | t.Errorf("FileInfo: Mode not equal (fs:%v != root:%v)", fi.Mode(), rfi.Mode()) 181 | } 182 | 183 | if fi.ModTime() != rfi.ModTime() { 184 | t.Errorf("FileInfo: ModTime not equal (fs:%v != root:%v)", fi.ModTime(), rfi.ModTime()) 185 | } 186 | 187 | if fi.IsDir() != rfi.IsDir() { 188 | t.Errorf("FileInfo: Mode not equal (fs:%v != root:%v)", fi.IsDir(), rfi.IsDir()) 189 | } 190 | 191 | if fi.Sys() != rfi.Sys() { 192 | t.Errorf("FileInfo: Sys not equal (fs:%v != root:%v)", fi.Sys(), rfi.Sys()) 193 | } 194 | } 195 | 196 | func TestReadDir(t *testing.T) { 197 | rfs := rootfs() 198 | fs := Create(rfs, prefixPath) 199 | 200 | err := fs.Mkdir("dir", 0777) 201 | if err != nil { 202 | t.Errorf("Mkdir: %v", err) 203 | } 204 | 205 | _, err = rfs.Stat(prefix("dir")) 206 | if os.IsNotExist(err) { 207 | t.Errorf("root:%v not found (%v)", prefix("dir"), err) 208 | } 209 | 210 | f, err := fs.OpenFile("dir/file", os.O_CREATE, 0666) 211 | defer f.Close() 212 | if err != nil { 213 | t.Errorf("OpenFile: %v", err) 214 | } 215 | 216 | s, err := fs.ReadDir("dir") 217 | if err != nil { 218 | t.Errorf("fs.ReadDir: %v", err) 219 | } 220 | 221 | rs, err := rfs.ReadDir(prefix("dir")) 222 | if err != nil { 223 | t.Errorf("rfs.ReadDir: %v", err) 224 | } 225 | 226 | if !reflect.DeepEqual(s, rs) { 227 | t.Error("ReadDir: slices not equal") 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /readonly.go: -------------------------------------------------------------------------------- 1 | package vfs 2 | 3 | import ( 4 | "errors" 5 | "os" 6 | ) 7 | 8 | // ReadOnly creates a readonly wrapper around the given filesystem. 9 | // It disables the following operations: 10 | // 11 | // - Create 12 | // - Remove 13 | // - Rename 14 | // - Mkdir 15 | // 16 | // And disables OpenFile flags: os.O_CREATE, os.O_APPEND, os.O_WRONLY 17 | // 18 | // OpenFile returns a File with disabled Write() method otherwise. 19 | func ReadOnly(fs Filesystem) *RoFS { 20 | return &RoFS{Filesystem: fs} 21 | } 22 | 23 | // RoFS represents a read-only filesystem and 24 | // works as a wrapper around existing filesystems. 25 | type RoFS struct { 26 | Filesystem 27 | } 28 | 29 | // ErrorReadOnly is returned on every disabled operation. 30 | var ErrReadOnly = errors.New("Filesystem is read-only") 31 | 32 | // Remove is disabled and returns ErrorReadOnly 33 | func (fs RoFS) Remove(name string) error { 34 | return ErrReadOnly 35 | } 36 | 37 | // Rename is disabled and returns ErrorReadOnly 38 | func (fs RoFS) Rename(oldpath, newpath string) error { 39 | return ErrReadOnly 40 | } 41 | 42 | // Mkdir is disabled and returns ErrorReadOnly 43 | func (fs RoFS) Mkdir(name string, perm os.FileMode) error { 44 | return ErrReadOnly 45 | } 46 | 47 | // OpenFile returns ErrorReadOnly if flag contains os.O_CREATE, os.O_APPEND, os.O_WRONLY. 48 | // Otherwise it returns a read-only File with disabled Write(..) operation. 49 | func (fs RoFS) OpenFile(name string, flag int, perm os.FileMode) (File, error) { 50 | if flag&os.O_CREATE == os.O_CREATE { 51 | return nil, ErrReadOnly 52 | } 53 | if flag&os.O_APPEND == os.O_APPEND { 54 | return nil, ErrReadOnly 55 | } 56 | if flag&os.O_WRONLY == os.O_WRONLY { 57 | return nil, ErrReadOnly 58 | } 59 | f, err := fs.Filesystem.OpenFile(name, flag, perm) 60 | if err != nil { 61 | return ReadOnlyFile(f), err 62 | } 63 | return ReadOnlyFile(f), nil 64 | } 65 | 66 | // ReadOnlyFile wraps the given file and disables Write(..) operation. 67 | func ReadOnlyFile(f File) File { 68 | return &roFile{f} 69 | } 70 | 71 | type roFile struct { 72 | File 73 | } 74 | 75 | // Write is disabled and returns ErrorReadOnly 76 | func (f roFile) Write(p []byte) (n int, err error) { 77 | return 0, ErrReadOnly 78 | } 79 | -------------------------------------------------------------------------------- /readonly_test.go: -------------------------------------------------------------------------------- 1 | package vfs 2 | 3 | import ( 4 | "errors" 5 | "os" 6 | "testing" 7 | ) 8 | 9 | var ( 10 | errDummy = errors.New("Not implemented") 11 | // Complete dummy base 12 | baseFSDummy = Dummy(errDummy) 13 | ro = ReadOnly(baseFSDummy) 14 | ) 15 | 16 | func TestROInterface(t *testing.T) { 17 | _ = Filesystem(ro) 18 | } 19 | 20 | func TestROOpenFileFlags(t *testing.T) { 21 | _, err := ro.OpenFile("name", os.O_CREATE, 0666) 22 | if err != ErrReadOnly { 23 | t.Errorf("Create error expected") 24 | } 25 | 26 | _, err = ro.OpenFile("name", os.O_APPEND, 0666) 27 | if err != ErrReadOnly { 28 | t.Errorf("Append error expected") 29 | } 30 | 31 | _, err = ro.OpenFile("name", os.O_WRONLY, 0666) 32 | if err != ErrReadOnly { 33 | t.Errorf("WROnly error expected") 34 | } 35 | 36 | _, err = ro.OpenFile("name", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) 37 | if err != ErrReadOnly { 38 | t.Errorf("Error expected") 39 | } 40 | 41 | // os.O_RDWR is allowed, dummy error is returned 42 | _, err = ro.OpenFile("name", os.O_RDWR, 0) 43 | if err != errDummy { 44 | t.Errorf("Expected dummy error") 45 | } 46 | } 47 | 48 | func TestRORemove(t *testing.T) { 49 | err := ro.Remove("test") 50 | if err != ErrReadOnly { 51 | t.Errorf("Remove error expected") 52 | } 53 | } 54 | 55 | func TestRORename(t *testing.T) { 56 | err := ro.Rename("old", "new") 57 | if err != ErrReadOnly { 58 | t.Errorf("Rename error expected") 59 | } 60 | } 61 | 62 | func TestMkDir(t *testing.T) { 63 | err := ro.Mkdir("test", 0777) 64 | if err != ErrReadOnly { 65 | t.Errorf("Mkdir error expected") 66 | } 67 | } 68 | 69 | type writeDummyFS struct { 70 | Filesystem 71 | } 72 | 73 | // Opens a dummyfile instead of error 74 | func (fs writeDummyFS) OpenFile(name string, flag int, perm os.FileMode) (File, error) { 75 | return DummyFile(errDummy), nil 76 | } 77 | 78 | func TestROOpenFileWrite(t *testing.T) { 79 | // Dummy base with mocked OpenFile for write test 80 | roWriteMock := ReadOnly(writeDummyFS{Dummy(errDummy)}) 81 | 82 | f, err := roWriteMock.OpenFile("name", os.O_RDWR, 0) 83 | if err != nil { 84 | t.Errorf("No OpenFile error expected: %s", err) 85 | } 86 | written, err := f.Write([]byte("test")) 87 | if err != ErrReadOnly { 88 | t.Errorf("Error expected: %s", err) 89 | } 90 | if written > 0 { 91 | t.Errorf("Written expected 0: %d", written) 92 | } 93 | } 94 | --------------------------------------------------------------------------------