// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package pathfs import ( "io" "log" "os" "path/filepath" "syscall" "github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/fuse/nodefs" ) type loopbackFileSystem struct { // TODO - this should need default fill in. FileSystem Root string } // A FUSE filesystem that shunts all request to an underlying file // system. Its main purpose is to provide test coverage without // having to build a synthetic filesystem. func NewLoopbackFileSystem(root string) FileSystem { // Make sure the Root path is absolute to avoid problems when the // application changes working directory. root, err := filepath.Abs(root) if err != nil { panic(err) } return &loopbackFileSystem{ FileSystem: NewDefaultFileSystem(), Root: root, } } func (fs *loopbackFileSystem) StatFs(name string) *fuse.StatfsOut { s := syscall.Statfs_t{} err := syscall.Statfs(fs.GetPath(name), &s) if err == nil { out := &fuse.StatfsOut{} out.FromStatfsT(&s) return out } return nil } func (fs *loopbackFileSystem) OnMount(nodeFs *PathNodeFs) { } func (fs *loopbackFileSystem) OnUnmount() {} func (fs *loopbackFileSystem) GetPath(relPath string) string { return filepath.Join(fs.Root, relPath) } func (fs *loopbackFileSystem) GetAttr(name string, context *fuse.Context) (a *fuse.Attr, code fuse.Status) { fullPath := fs.GetPath(name) var err error = nil st := syscall.Stat_t{} if name == "" { // When GetAttr is called for the toplevel directory, we always want // to look through symlinks. err = syscall.Stat(fullPath, &st) } else { err = syscall.Lstat(fullPath, &st) } if err != nil { return nil, fuse.ToStatus(err) } a = &fuse.Attr{} a.FromStat(&st) return a, fuse.OK } func (fs *loopbackFileSystem) OpenDir(name string, context *fuse.Context) (stream []fuse.DirEntry, status fuse.Status) { // What other ways beyond O_RDONLY are there to open // directories? f, err := os.Open(fs.GetPath(name)) if err != nil { return nil, fuse.ToStatus(err) } want := 500 output := make([]fuse.DirEntry, 0, want) for { infos, err := f.Readdir(want) for i := range infos { // workaround for https://code.google.com/p/go/issues/detail?id=5960 if infos[i] == nil { continue } n := infos[i].Name() d := fuse.DirEntry{ Name: n, } if s := fuse.ToStatT(infos[i]); s != nil { d.Mode = uint32(s.Mode) d.Ino = s.Ino } else { log.Printf("ReadDir entry %q for %q has no stat info", n, name) } output = append(output, d) } if len(infos) < want || err == io.EOF { break } if err != nil { log.Println("Readdir() returned err:", err) break } } f.Close() return output, fuse.OK } func (fs *loopbackFileSystem) Open(name string, flags uint32, context *fuse.Context) (fuseFile nodefs.File, status fuse.Status) { f, err := os.OpenFile(fs.GetPath(name), int(flags), 0) if err != nil { return nil, fuse.ToStatus(err) } return nodefs.NewLoopbackFile(f), fuse.OK } func (fs *loopbackFileSystem) Chmod(path string, mode uint32, context *fuse.Context) (code fuse.Status) { err := os.Chmod(fs.GetPath(path), os.FileMode(mode)) return fuse.ToStatus(err) } func (fs *loopbackFileSystem) Chown(path string, uid uint32, gid uint32, context *fuse.Context) (code fuse.Status) { return fuse.ToStatus(os.Chown(fs.GetPath(path), int(uid), int(gid))) } func (fs *loopbackFileSystem) Truncate(path string, offset uint64, context *fuse.Context) (code fuse.Status) { return fuse.ToStatus(os.Truncate(fs.GetPath(path), int64(offset))) } func (fs *loopbackFileSystem) Readlink(name string, context *fuse.Context) (out string, code fuse.Status) { f, err := os.Readlink(fs.GetPath(name)) return f, fuse.ToStatus(err) } func (fs *loopbackFileSystem) Mknod(name string, mode uint32, dev uint32, context *fuse.Context) (code fuse.Status) { return fuse.ToStatus(syscall.Mknod(fs.GetPath(name), mode, int(dev))) } func (fs *loopbackFileSystem) Mkdir(path string, mode uint32, context *fuse.Context) (code fuse.Status) { return fuse.ToStatus(os.Mkdir(fs.GetPath(path), os.FileMode(mode))) } // Don't use os.Remove, it removes twice (unlink followed by rmdir). func (fs *loopbackFileSystem) Unlink(name string, context *fuse.Context) (code fuse.Status) { return fuse.ToStatus(syscall.Unlink(fs.GetPath(name))) } func (fs *loopbackFileSystem) Rmdir(name string, context *fuse.Context) (code fuse.Status) { return fuse.ToStatus(syscall.Rmdir(fs.GetPath(name))) } func (fs *loopbackFileSystem) Symlink(pointedTo string, linkName string, context *fuse.Context) (code fuse.Status) { return fuse.ToStatus(os.Symlink(pointedTo, fs.GetPath(linkName))) } func (fs *loopbackFileSystem) Rename(oldPath string, newPath string, context *fuse.Context) (codee fuse.Status) { err := os.Rename(fs.GetPath(oldPath), fs.GetPath(newPath)) return fuse.ToStatus(err) } func (fs *loopbackFileSystem) Link(orig string, newName string, context *fuse.Context) (code fuse.Status) { return fuse.ToStatus(os.Link(fs.GetPath(orig), fs.GetPath(newName))) } func (fs *loopbackFileSystem) Access(name string, mode uint32, context *fuse.Context) (code fuse.Status) { return fuse.ToStatus(syscall.Access(fs.GetPath(name), mode)) } func (fs *loopbackFileSystem) Create(path string, flags uint32, mode uint32, context *fuse.Context) (fuseFile nodefs.File, code fuse.Status) { f, err := os.OpenFile(fs.GetPath(path), int(flags)|os.O_CREATE, os.FileMode(mode)) return nodefs.NewLoopbackFile(f), fuse.ToStatus(err) }