Commit 67340a77 authored by Han-Wen Nienhuys's avatar Han-Wen Nienhuys

Move dummyfuse* into fuse/ , and use as provider for default methods.

parent 59d56e9f
......@@ -5,8 +5,7 @@ TARG=github.com/hanwen/go-fuse/examplelib
DEPS=../fuse
GOFILES=dummyfuse.go\
passthrough.go\
GOFILES=passthrough.go\
stackfs.go\
zipfs.go\
misc.go
......
package examplelib
import (
"github.com/hanwen/go-fuse/fuse"
)
// Declare dummy methods, for cut & paste convenience.
type DummyFuse struct{}
func (self *DummyFuse) Init(h *fuse.InHeader, input *fuse.InitIn) (*fuse.InitOut, fuse.Status) {
return new(fuse.InitOut), fuse.OK
}
func (self *DummyFuse) Destroy(h *fuse.InHeader, input *fuse.InitIn) {
}
func (self *DummyFuse) Lookup(h *fuse.InHeader, name string) (out *fuse.EntryOut, code fuse.Status) {
return nil, fuse.ENOSYS
}
func (self *DummyFuse) Forget(h *fuse.InHeader, input *fuse.ForgetIn) {
}
func (self *DummyFuse) GetAttr(header *fuse.InHeader, input *fuse.GetAttrIn) (out *fuse.AttrOut, code fuse.Status) {
return nil, fuse.ENOSYS
}
func (self *DummyFuse) Open(header *fuse.InHeader, input *fuse.OpenIn) (flags uint32, fuseFile fuse.RawFuseFile, status fuse.Status) {
return 0, nil, fuse.OK
}
func (self *DummyFuse) SetAttr(header *fuse.InHeader, input *fuse.SetAttrIn) (out *fuse.AttrOut, code fuse.Status) {
return nil, fuse.ENOSYS
}
func (self *DummyFuse) Readlink(header *fuse.InHeader) (out []byte, code fuse.Status) {
return nil, fuse.ENOSYS
}
func (self *DummyFuse) Mknod(header *fuse.InHeader, input *fuse.MknodIn, name string) (out *fuse.EntryOut, code fuse.Status) {
return new(fuse.EntryOut), fuse.ENOSYS
}
func (self *DummyFuse) Mkdir(header *fuse.InHeader, input *fuse.MkdirIn, name string) (out *fuse.EntryOut, code fuse.Status) {
return nil, fuse.ENOSYS
}
func (self *DummyFuse) Unlink(header *fuse.InHeader, name string) (code fuse.Status) {
return fuse.ENOSYS
}
func (self *DummyFuse) Rmdir(header *fuse.InHeader, name string) (code fuse.Status) {
return fuse.ENOSYS
}
func (self *DummyFuse) Symlink(header *fuse.InHeader, pointedTo string, linkName string) (out *fuse.EntryOut, code fuse.Status) {
return nil, fuse.ENOSYS
}
func (self *DummyFuse) Rename(header *fuse.InHeader, input *fuse.RenameIn, oldName string, newName string) (code fuse.Status) {
return fuse.ENOSYS
}
func (self *DummyFuse) Link(header *fuse.InHeader, input *fuse.LinkIn, name string) (out *fuse.EntryOut, code fuse.Status) {
return nil, fuse.ENOSYS
}
func (self *DummyFuse) SetXAttr(header *fuse.InHeader, input *fuse.SetXAttrIn) fuse.Status {
return fuse.ENOSYS
}
func (self *DummyFuse) GetXAttr(header *fuse.InHeader, input *fuse.GetXAttrIn) (out *fuse.GetXAttrOut, code fuse.Status) {
return nil, fuse.ENOSYS
}
func (self *DummyFuse) Access(header *fuse.InHeader, input *fuse.AccessIn) (code fuse.Status) {
return fuse.ENOSYS
}
func (self *DummyFuse) Create(header *fuse.InHeader, input *fuse.CreateIn, name string) (flags uint32, fuseFile fuse.RawFuseFile, out *fuse.EntryOut, code fuse.Status) {
return 0, nil, nil, fuse.ENOSYS
}
func (self *DummyFuse) Bmap(header *fuse.InHeader, input *fuse.BmapIn) (out *fuse.BmapOut, code fuse.Status) {
return nil, fuse.ENOSYS
}
func (self *DummyFuse) Ioctl(header *fuse.InHeader, input *fuse.IoctlIn) (out *fuse.IoctlOut, code fuse.Status) {
return nil, fuse.ENOSYS
}
func (self *DummyFuse) Poll(header *fuse.InHeader, input *fuse.PollIn) (out *fuse.PollOut, code fuse.Status) {
return nil, fuse.ENOSYS
}
func (self *DummyFuse) OpenDir(header *fuse.InHeader, input *fuse.OpenIn) (flags uint32, fuseFile fuse.RawFuseDir, status fuse.Status) {
return 0, nil, fuse.ENOSYS
}
func (self *DummyFuse) Release(header *fuse.InHeader, f fuse.RawFuseFile) {
}
func (self *DummyFuse) ReleaseDir(header *fuse.InHeader, f fuse.RawFuseDir) {
}
////////////////////////////////////////////////////////////////
// DummyFuseFile
type DummyFuseFile struct{}
func (self *DummyFuseFile) Read(*fuse.ReadIn, *fuse.BufferPool) ([]byte, fuse.Status) {
return []byte(""), fuse.ENOSYS
}
func (self *DummyFuseFile) Write(*fuse.WriteIn, []byte) (uint32, fuse.Status) {
return 0, fuse.ENOSYS
}
func (self *DummyFuseFile) Flush() fuse.Status {
return fuse.ENOSYS
}
func (self *DummyFuseFile) Release() {
}
func (self *DummyFuseFile) Fsync(*fuse.FsyncIn) (code fuse.Status) {
return fuse.ENOSYS
}
func (self *DummyFuseFile) ReadDir(input *fuse.ReadIn) (*fuse.DirEntryList, fuse.Status) {
return nil, fuse.ENOSYS
}
func (self *DummyFuseFile) ReleaseDir() {
}
func (self *DummyFuseFile) FsyncDir(input *fuse.FsyncIn) (code fuse.Status) {
return fuse.ENOSYS
}
////////////////////////////////////////////////////////////////
// DummyPathFuse
type DummyPathFuse struct{}
func (self *DummyPathFuse) GetAttr(name string) (*fuse.Attr, fuse.Status) {
return nil, fuse.ENOSYS
}
func (self *DummyPathFuse) Readlink(name string) (string, fuse.Status) {
return "", fuse.ENOSYS
}
func (self *DummyPathFuse) Mknod(name string, mode uint32, dev uint32) fuse.Status {
return fuse.ENOSYS
}
func (self *DummyPathFuse) Mkdir(name string, mode uint32) fuse.Status {
return fuse.ENOSYS
}
func (self *DummyPathFuse) Unlink(name string) (code fuse.Status) {
return fuse.ENOSYS
}
func (self *DummyPathFuse) Rmdir(name string) (code fuse.Status) {
return fuse.ENOSYS
}
func (self *DummyPathFuse) Symlink(value string, linkName string) (code fuse.Status) {
return fuse.ENOSYS
}
func (self *DummyPathFuse) Rename(oldName string, newName string) (code fuse.Status) {
return fuse.ENOSYS
}
func (self *DummyPathFuse) Link(oldName string, newName string) (code fuse.Status) {
return fuse.ENOSYS
}
func (self *DummyPathFuse) Chmod(name string, mode uint32) (code fuse.Status) {
return fuse.ENOSYS
}
func (self *DummyPathFuse) Chown(name string, uid uint32, gid uint32) (code fuse.Status) {
return fuse.ENOSYS
}
func (self *DummyPathFuse) Truncate(name string, offset uint64) (code fuse.Status) {
return fuse.ENOSYS
}
func (self *DummyPathFuse) Open(name string, flags uint32) (file fuse.RawFuseFile, code fuse.Status) {
return nil, fuse.ENOSYS
}
func (self *DummyPathFuse) OpenDir(name string) (stream chan fuse.DirEntry, status fuse.Status) {
return nil, fuse.ENOSYS
}
func (self *DummyPathFuse) Mount(conn *fuse.PathFileSystemConnector) fuse.Status {
return fuse.OK
}
func (self *DummyPathFuse) Unmount() {
}
func (self *DummyPathFuse) Access(name string, mode uint32) (code fuse.Status) {
return fuse.ENOSYS
}
func (self *DummyPathFuse) Create(name string, flags uint32, mode uint32) (file fuse.RawFuseFile, code fuse.Status) {
return nil, fuse.ENOSYS
}
func (self *DummyPathFuse) Utimens(name string, AtimeNs uint64, CtimeNs uint64) (code fuse.Status) {
return fuse.ENOSYS
}
......@@ -16,6 +16,8 @@ var _ = fmt.Println
type PassThroughFuse struct {
root string
fuse.DefaultPathFilesystem
}
func NewPassThroughFuse(root string) (out *PassThroughFuse) {
......@@ -25,14 +27,6 @@ func NewPassThroughFuse(root string) (out *PassThroughFuse) {
return out
}
func (self *PassThroughFuse) Mount(conn *fuse.PathFileSystemConnector) fuse.Status {
return fuse.OK
}
func (self *PassThroughFuse) Unmount() {
}
func (self *PassThroughFuse) GetPath(relPath string) string {
return path.Join(self.root, relPath)
}
......@@ -160,6 +154,8 @@ func (self *PassThroughFuse) SetOptions(options *fuse.PathFileSystemConnectorOpt
type PassThroughFile struct {
file *os.File
fuse.DefaultRawFuseFile
}
func (self *PassThroughFile) Read(input *fuse.ReadIn, buffers *fuse.BufferPool) ([]byte, fuse.Status) {
......@@ -178,10 +174,6 @@ func (self *PassThroughFile) Write(input *fuse.WriteIn, data []byte) (uint32, fu
return uint32(n), fuse.OsErrorToFuseError(err)
}
func (self *PassThroughFile) Flush() fuse.Status {
return fuse.OK
}
func (self *PassThroughFile) Release() {
self.file.Close()
}
......
......@@ -253,11 +253,9 @@ func (self *SubmountFileSystem) forget(h *fuse.InHeader, input *fuse.ForgetIn) *
return subNodeData
}
////////////////////////////////////////////////////////////////
// Functions below should not need locking primitives.
// Caller should init after this returns successfully.
func (self *SubmountFileSystem) AddFileSystem(name string, fs fuse.RawFileSystem, attr fuse.Attr) bool {
ok := self.addFileSystem(name, fs, attr)
......@@ -607,6 +605,8 @@ type SubmountFileSystemTopDir struct {
names []string
modes []uint32
nextRead int
fuse.DefaultRawFuseDir
}
func NewSubmountFileSystemTopDir(fs *SubmountFileSystem) *SubmountFileSystemTopDir {
......@@ -629,11 +629,3 @@ func (self *SubmountFileSystemTopDir) ReadDir(input *fuse.ReadIn) (*fuse.DirEntr
}
return de, fuse.OK
}
func (self *SubmountFileSystemTopDir) ReleaseDir() {
}
func (self *SubmountFileSystemTopDir) FsyncDir(input *fuse.FsyncIn) (code fuse.Status) {
return fuse.ENOENT
}
package examplelib
import (
"github.com/hanwen/go-fuse/fuse"
"archive/zip"
"fmt"
"os"
"strings"
"path"
"log"
)
var _ = log.Printf
type ZipDirTree struct {
subdirs map[string]*ZipDirTree
files map[string]*zip.File
}
func NewZipDirTree() *ZipDirTree {
d := new(ZipDirTree)
d.subdirs = make(map[string]*ZipDirTree)
d.files = make(map[string]*zip.File)
return d
}
func (me *ZipDirTree) Print(indent int) {
s := ""
for i := 0; i < indent; i++ {
s = s + " "
}
for k, v := range me.subdirs {
fmt.Println(s + k + ":")
v.Print(indent + 2)
}
for k, _ := range me.files {
fmt.Println(s + k)
}
}
func (me *ZipDirTree) Lookup(name string) (*ZipDirTree, *zip.File) {
if name == "" {
return me, nil
}
parent := me
comps := strings.Split(path.Clean(name), "/", -1)
for _, c := range comps[:len(comps)-1] {
parent = parent.subdirs[c]
if parent == nil {
return nil, nil
}
}
base := comps[len(comps)-1]
file, ok := parent.files[base]
if ok {
return parent, file
}
return parent.subdirs[base], nil
}
func (me *ZipDirTree) FindDir(name string) *ZipDirTree {
s, ok := me.subdirs[name]
if !ok {
s = NewZipDirTree()
me.subdirs[name] = s
}
return s
}
type ZipFileFuse struct {
zipReader *zip.Reader
tree *ZipDirTree
fuse.DefaultPathFilesystem
}
func zipFilesToTree(files []*zip.File) *ZipDirTree {
t := NewZipDirTree()
for _, f := range files {
parent := t
comps := strings.Split(path.Clean(f.Name), "/", -1)
base := ""
// Ugh - zip files have directories separate.
if !strings.HasSuffix(f.Name, "/") {
base = comps[len(comps)-1]
comps = comps[:len(comps)-1]
}
for _, c := range comps {
parent = parent.FindDir(c)
}
if base != "" {
parent.files[base] = f
}
}
return t
}
func NewZipFileFuse(name string) *ZipFileFuse {
z := new(ZipFileFuse)
r, err := zip.OpenReader(name)
if err != nil {
panic("zip open error")
}
z.zipReader = r
z.tree = zipFilesToTree(r.File)
return z
}
const zip_DIRMODE uint32 = fuse.S_IFDIR | 0700
const zip_FILEMODE uint32 = fuse.S_IFREG | 0600
func (self *ZipFileFuse) GetAttr(name string) (*fuse.Attr, fuse.Status) {
dir, file := self.tree.Lookup(name)
if dir == nil {
return nil, fuse.ENOENT
}
a := new(fuse.Attr)
if file == nil {
a.Mode = zip_DIRMODE
} else {
a.Mode = zip_FILEMODE
a.Size = uint64(file.UncompressedSize)
}
return a, fuse.OK
}
func (self *ZipFileFuse) Open(name string, flags uint32) (file fuse.RawFuseFile, code fuse.Status) {
_, zfile := self.tree.Lookup(name)
if zfile == nil {
return nil, fuse.ENOENT
}
return NewZipFile(zfile), fuse.OK
}
func (self *ZipFileFuse) OpenDir(name string) (stream chan fuse.DirEntry, code fuse.Status) {
zdir, file := self.tree.Lookup(name)
if file != nil {
return nil, fuse.ENOSYS
}
if zdir == nil {
panic("zdir")
}
stream = make(chan fuse.DirEntry)
go func() {
for k, _ := range zdir.files {
stream <- fuse.DirEntry{
Name: k,
Mode: zip_FILEMODE,
}
}
for k, _ := range zdir.subdirs {
stream <- fuse.DirEntry{
Name: k,
Mode: zip_DIRMODE,
}
}
close(stream)
}()
return stream, fuse.OK
}
////////////////////////////////////////////////////////////////
// files & dirs
type ZipFile struct {
data []byte
fuse.DefaultRawFuseFile
}
func NewZipFile(f *zip.File) *ZipFile {
z := ZipFile{
data: make([]byte, f.UncompressedSize),
}
rc, err := f.Open()
if err != nil {
panic("zip open")
}
start := 0
for {
n, err := rc.Read(z.data[start:])
start += n
if err == os.EOF {
break
}
if err != nil && err != os.EOF {
panic(fmt.Sprintf("read err: %v, n %v, sz %v", err, n, len(z.data)))
}
}
return &z
}
func (self *ZipFile) Read(input *fuse.ReadIn, bp *fuse.BufferPool) ([]byte, fuse.Status) {
end := int(input.Offset) + int(input.Size)
if end > len(self.data) {
end = len(self.data)
}
// TODO - robustify bufferpool
return self.data[input.Offset:end], fuse.OK
}
......@@ -10,7 +10,8 @@ GOFILES=misc.go\
mount.go\
types.go\
pathfilesystem.go \
bufferpool.go
bufferpool.go \
default.go
include $(GOROOT)/src/Make.pkg
package fuse
func (self *DefaultRawFuseFileSystem) Init(h *InHeader, input *InitIn) (*InitOut, Status) {
return new(InitOut), OK
}
func (self *DefaultRawFuseFileSystem) Destroy(h *InHeader, input *InitIn) {
}
func (self *DefaultRawFuseFileSystem) Lookup(h *InHeader, name string) (out *EntryOut, code Status) {
return nil, ENOSYS
}
func (self *DefaultRawFuseFileSystem) Forget(h *InHeader, input *ForgetIn) {
}
func (self *DefaultRawFuseFileSystem) GetAttr(header *InHeader, input *GetAttrIn) (out *AttrOut, code Status) {
return nil, ENOSYS
}
func (self *DefaultRawFuseFileSystem) Open(header *InHeader, input *OpenIn) (flags uint32, fuseFile RawFuseFile, status Status) {
return 0, nil, OK
}
func (self *DefaultRawFuseFileSystem) SetAttr(header *InHeader, input *SetAttrIn) (out *AttrOut, code Status) {
return nil, ENOSYS
}
func (self *DefaultRawFuseFileSystem) Readlink(header *InHeader) (out []byte, code Status) {
return nil, ENOSYS
}
func (self *DefaultRawFuseFileSystem) Mknod(header *InHeader, input *MknodIn, name string) (out *EntryOut, code Status) {
return new(EntryOut), ENOSYS
}
func (self *DefaultRawFuseFileSystem) Mkdir(header *InHeader, input *MkdirIn, name string) (out *EntryOut, code Status) {
return nil, ENOSYS
}
func (self *DefaultRawFuseFileSystem) Unlink(header *InHeader, name string) (code Status) {
return ENOSYS
}
func (self *DefaultRawFuseFileSystem) Rmdir(header *InHeader, name string) (code Status) {
return ENOSYS
}
func (self *DefaultRawFuseFileSystem) Symlink(header *InHeader, pointedTo string, linkName string) (out *EntryOut, code Status) {
return nil, ENOSYS
}
func (self *DefaultRawFuseFileSystem) Rename(header *InHeader, input *RenameIn, oldName string, newName string) (code Status) {
return ENOSYS
}
func (self *DefaultRawFuseFileSystem) Link(header *InHeader, input *LinkIn, name string) (out *EntryOut, code Status) {
return nil, ENOSYS
}
func (self *DefaultRawFuseFileSystem) SetXAttr(header *InHeader, input *SetXAttrIn) Status {
return ENOSYS
}
func (self *DefaultRawFuseFileSystem) GetXAttr(header *InHeader, input *GetXAttrIn) (out *GetXAttrOut, code Status) {
return nil, ENOSYS
}
func (self *DefaultRawFuseFileSystem) Access(header *InHeader, input *AccessIn) (code Status) {
return ENOSYS
}
func (self *DefaultRawFuseFileSystem) Create(header *InHeader, input *CreateIn, name string) (flags uint32, fuseFile RawFuseFile, out *EntryOut, code Status) {
return 0, nil, nil, ENOSYS
}
func (self *DefaultRawFuseFileSystem) Bmap(header *InHeader, input *BmapIn) (out *BmapOut, code Status) {
return nil, ENOSYS
}
func (self *DefaultRawFuseFileSystem) Ioctl(header *InHeader, input *IoctlIn) (out *IoctlOut, code Status) {
return nil, ENOSYS
}
func (self *DefaultRawFuseFileSystem) Poll(header *InHeader, input *PollIn) (out *PollOut, code Status) {
return nil, ENOSYS
}
func (self *DefaultRawFuseFileSystem) OpenDir(header *InHeader, input *OpenIn) (flags uint32, fuseFile RawFuseDir, status Status) {
return 0, nil, ENOSYS
}
func (self *DefaultRawFuseFileSystem) Release(header *InHeader, f RawFuseFile) {
}
func (self *DefaultRawFuseFileSystem) ReleaseDir(header *InHeader, f RawFuseDir) {
}
////////////////////////////////////////////////////////////////
// DefaultRawFuseFile
func (self *DefaultRawFuseFile) Read(*ReadIn, *BufferPool) ([]byte, Status) {
return []byte(""), ENOSYS
}
func (self *DefaultRawFuseFile) Write(*WriteIn, []byte) (uint32, Status) {
return 0, ENOSYS
}
func (self *DefaultRawFuseFile) Flush() Status {
return ENOSYS
}
func (self *DefaultRawFuseFile) Release() {
}
func (self *DefaultRawFuseFile) Fsync(*FsyncIn) (code Status) {
return ENOSYS
}
////////////////////////////////////////////////////////////////
//
func (self *DefaultRawFuseDir) ReadDir(input *ReadIn) (*DirEntryList, Status) {
return nil, ENOSYS
}
func (self *DefaultRawFuseDir) ReleaseDir() {
}
func (self *DefaultRawFuseDir) FsyncDir(input *FsyncIn) (code Status) {
return ENOSYS
}
////////////////////////////////////////////////////////////////
// DefaultPathFilesystem
func (self *DefaultPathFilesystem) GetAttr(name string) (*Attr, Status) {
return nil, ENOSYS
}
func (self *DefaultPathFilesystem) Readlink(name string) (string, Status) {
return "", ENOSYS
}
func (self *DefaultPathFilesystem) Mknod(name string, mode uint32, dev uint32) Status {
return ENOSYS
}
func (self *DefaultPathFilesystem) Mkdir(name string, mode uint32) Status {
return ENOSYS
}
func (self *DefaultPathFilesystem) Unlink(name string) (code Status) {
return ENOSYS
}
func (self *DefaultPathFilesystem) Rmdir(name string) (code Status) {
return ENOSYS
}
func (self *DefaultPathFilesystem) Symlink(value string, linkName string) (code Status) {
return ENOSYS
}
func (self *DefaultPathFilesystem) Rename(oldName string, newName string) (code Status) {
return ENOSYS
}
func (self *DefaultPathFilesystem) Link(oldName string, newName string) (code Status) {
return ENOSYS
}
func (self *DefaultPathFilesystem) Chmod(name string, mode uint32) (code Status) {
return ENOSYS
}
func (self *DefaultPathFilesystem) Chown(name string, uid uint32, gid uint32) (code Status) {
return ENOSYS
}
func (self *DefaultPathFilesystem) Truncate(name string, offset uint64) (code Status) {
return ENOSYS
}
func (self *DefaultPathFilesystem) Open(name string, flags uint32) (file RawFuseFile, code Status) {
return nil, ENOSYS
}
func (self *DefaultPathFilesystem) OpenDir(name string) (stream chan DirEntry, status Status) {
return nil, ENOSYS
}
func (self *DefaultPathFilesystem) Mount(conn *PathFileSystemConnector) Status {
return OK
}
func (self *DefaultPathFilesystem) Unmount() {
}
func (self *DefaultPathFilesystem) Access(name string, mode uint32) (code Status) {
return ENOSYS
}
func (self *DefaultPathFilesystem) Create(name string, flags uint32, mode uint32) (file RawFuseFile, code Status) {
return nil, ENOSYS
}
func (self *DefaultPathFilesystem) Utimens(name string, AtimeNs uint64, CtimeNs uint64) (code Status) {
return ENOSYS
}
package examplelib
package fuse
// Compilation test for DummyFuse and DummyPathFuse
import (
"github.com/hanwen/go-fuse/fuse"
"testing"
)
func TestDummy(t *testing.T) {
fs := new(DummyFuse)
fuse.NewMountState(fs)
fs := new(DefaultRawFuseFileSystem)
NewMountState(fs)
pathFs := new(DummyPathFuse)
pathFs := new(DefaultPathFilesystem)
fuse.NewPathFileSystemConnector(pathFs)
NewPathFileSystemConnector(pathFs)
}
func TestDummyFile(t *testing.T) {
d := new(DummyFuseFile)
var filePtr fuse.RawFuseFile = d
var fileDir fuse.RawFuseDir = d
d := new(DefaultRawFuseFile)
var filePtr RawFuseFile = d
d2 := new(DefaultRawFuseDir)
var fileDir RawFuseDir = d2
_ = fileDir
_ = filePtr
}
......@@ -69,6 +69,8 @@ type FuseDir struct {
leftOver DirEntry
connector *PathFileSystemConnector
parentIno uint64
DefaultRawFuseDir
}
func (me *FuseDir) inode(name string) uint64 {
......@@ -111,6 +113,3 @@ func (me *FuseDir) ReleaseDir() {
close(me.stream)
}
func (me *FuseDir) FsyncDir(input *FsyncIn) (code Status) {
return ENOSYS
}
......@@ -583,3 +583,12 @@ type PathFilesystem interface {
// unimplemented: poll, ioctl, bmap.
}
// Include this method in your implementation to inherit default nop
// implementations.
type DefaultRawFuseDir struct {}
type DefaultPathFilesystem struct {}
type DefaultRawFuseFile struct {}
type DefaultRawFuseFileSystem struct{}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment