Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds an in-memory filesystem example #83

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@

/clockfs
/hellofs
/memfs
391 changes: 391 additions & 0 deletions examples/memfs/memfs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,391 @@
// Memfs implements an in-memory file system.
package main

import (
"flag"
"fmt"
"log"
"os"
"sync"
"sync/atomic"
"time"

"bazil.org/fuse"
"bazil.org/fuse/fs"
"golang.org/x/net/context"
)

// debug flag enables logging of debug messages to stderr.
var debug = flag.Bool("debug", false, "enable debug log messages to stderr")

func usage() {
fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
fmt.Fprintf(os.Stderr, " %s MOUNTPOINT\n", os.Args[0])
flag.PrintDefaults()
}

func debugLog(msg interface{}) {
fmt.Fprintf(os.Stderr, "%v\n", msg)
}

func main() {
flag.Usage = usage
flag.Parse()

if flag.NArg() != 1 {
usage()
os.Exit(2)
}

mountpoint := flag.Arg(0)
c, err := fuse.Mount(
mountpoint,
fuse.FSName("memfs"),
fuse.Subtype("memfs"),
fuse.LocalVolume(),
fuse.VolumeName("Memory FS"),
)
if err != nil {
log.Fatal(err)
}
defer c.Close()

cfg := &fs.Config{}
if *debug {
cfg.Debug = debugLog
}
srv := fs.New(c, cfg)
filesys := NewMemFS()

if err := srv.Serve(filesys); err != nil {
log.Fatal(err)
}

// Check if the mount process has an error to report.
<-c.Ready
if err := c.MountError; err != nil {
log.Fatal(err)
}
}

type MemFS struct {
root *Dir
nodeId uint64

nodeCount uint64
size int64
}

// Compile-time interface checks.
var _ fs.FS = (*MemFS)(nil)
var _ fs.FSStatfser = (*MemFS)(nil)

var _ fs.Node = (*Dir)(nil)
var _ fs.NodeCreater = (*Dir)(nil)
var _ fs.NodeMkdirer = (*Dir)(nil)
var _ fs.NodeRemover = (*Dir)(nil)
var _ fs.NodeRenamer = (*Dir)(nil)
var _ fs.NodeStringLookuper = (*Dir)(nil)

var _ fs.HandleReadAller = (*File)(nil)
var _ fs.HandleWriter = (*File)(nil)
var _ fs.Node = (*File)(nil)
var _ fs.NodeOpener = (*File)(nil)
var _ fs.NodeSetattrer = (*File)(nil)

func NewMemFS() *MemFS {
fs := &MemFS{
nodeCount: 1,
}
fs.root = fs.newDir(os.ModeDir | 0777)
if fs.root.attr.Inode != 1 {
panic("Root node should have been assigned id 1")
}
return fs
}

func (m *MemFS) nextId() uint64 {
return atomic.AddUint64(&m.nodeId, 1)
}

func (m *MemFS) newDir(mode os.FileMode) *Dir {
n := time.Now()
return &Dir{
attr: fuse.Attr{
Inode: m.nextId(),
Atime: n,
Mtime: n,
Ctime: n,
Crtime: n,
Mode: os.ModeDir | mode,
},
fs: m,
nodes: make(map[string]fs.Node),
}
}

func (m *MemFS) newFile(mode os.FileMode) *File {
n := time.Now()
return &File{
attr: fuse.Attr{
Inode: m.nextId(),
Atime: n,
Mtime: n,
Ctime: n,
Crtime: n,
Mode: mode,
},
data: make([]byte, 0),
}
}

type Dir struct {
sync.RWMutex
attr fuse.Attr

fs *MemFS
parent *Dir
nodes map[string]fs.Node
}

type File struct {
sync.RWMutex
attr fuse.Attr

fs *MemFS
data []byte
}

func (f *MemFS) Root() (fs.Node, error) {
return f.root, nil
}

func (f *MemFS) Statfs(ctx context.Context, req *fuse.StatfsRequest,
resp *fuse.StatfsResponse) error {
resp.Blocks = uint64((atomic.LoadInt64(&f.size) + 511) / 512)
resp.Bsize = 512
resp.Files = atomic.LoadUint64(&f.nodeCount)
return nil
}

func (f *File) Attr(ctx context.Context, o *fuse.Attr) error {
f.RLock()
*o = f.attr
f.RUnlock()
return nil
}

func (d *Dir) Attr(ctx context.Context, o *fuse.Attr) error {
d.RLock()
*o = d.attr
d.RUnlock()
return nil
}

func (d *Dir) Lookup(ctx context.Context, name string) (fs.Node, error) {
d.RLock()
n, exist := d.nodes[name]
d.RUnlock()

if !exist {
return nil, fuse.ENOENT
}
return n, nil
}

func (d *Dir) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) {
d.RLock()
dirs := make([]fuse.Dirent, len(d.nodes)+2)

// Add special references.
dirs[0] = fuse.Dirent{
Name: ".",
Inode: d.attr.Inode,
Type: fuse.DT_Dir,
}
dirs[1] = fuse.Dirent{
Name: "..",
Type: fuse.DT_Dir,
}
if d.parent != nil {
dirs[1].Inode = d.parent.attr.Inode
} else {
dirs[1].Inode = d.attr.Inode
}

// Add remaining files.
idx := 2
for name, node := range d.nodes {
ent := fuse.Dirent{
Name: name,
}
switch n := node.(type) {
case *File:
ent.Inode = n.attr.Inode
ent.Type = fuse.DT_File
case *Dir:
ent.Inode = n.attr.Inode
ent.Type = fuse.DT_Dir
}
dirs[idx] = ent
idx++
}
d.RUnlock()
return dirs, nil
}

func (d *Dir) Mkdir(ctx context.Context, req *fuse.MkdirRequest) (fs.Node, error) {
d.Lock()
defer d.Unlock()

if _, exists := d.nodes[req.Name]; exists {
return nil, fuse.EEXIST
}

n := d.fs.newDir(req.Mode)
d.nodes[req.Name] = n
atomic.AddUint64(&d.fs.nodeCount, 1)

return n, nil
}

func (d *Dir) Create(ctx context.Context, req *fuse.CreateRequest,
resp *fuse.CreateResponse) (fs.Node, fs.Handle, error) {
d.Lock()
defer d.Unlock()

if _, exists := d.nodes[req.Name]; exists {
return nil, nil, fuse.EEXIST
}

n := d.fs.newFile(req.Mode)
n.fs = d.fs
d.nodes[req.Name] = n
atomic.AddUint64(&d.fs.nodeCount, 1)

resp.Attr = n.attr

return n, n, nil
}

func (d *Dir) Rename(ctx context.Context, req *fuse.RenameRequest, newDir fs.Node) error {
nd := newDir.(*Dir)
if d.attr.Inode == nd.attr.Inode {
d.Lock()
defer d.Unlock()
} else if d.attr.Inode < nd.attr.Inode {
d.Lock()
defer d.Unlock()
nd.Lock()
defer nd.Unlock()
} else {
nd.Lock()
defer nd.Unlock()
d.Lock()
defer d.Unlock()
}

if _, exists := d.nodes[req.OldName]; !exists {
return fuse.ENOENT
}

// Rename can be used as an atomic replace, override an existing file.
if old, exists := nd.nodes[req.NewName]; exists {
atomic.AddUint64(&d.fs.nodeCount, ^uint64(0)) // decrement by one
if oldFile, ok := old.(*File); !ok {
atomic.AddInt64(&d.fs.size, -int64(oldFile.attr.Size))
}
}

nd.nodes[req.NewName] = d.nodes[req.OldName]
delete(d.nodes, req.OldName)
return nil
}

func (d *Dir) Remove(ctx context.Context, req *fuse.RemoveRequest) error {
d.Lock()
defer d.Unlock()

if n, exists := d.nodes[req.Name]; !exists {
return fuse.ENOENT
} else if req.Dir && len(n.(*Dir).nodes) > 0 {
return fuse.ENOTEMPTY
}

delete(d.nodes, req.Name)
atomic.AddUint64(&d.fs.nodeCount, ^uint64(0)) // decrement by one
return nil
}

func (f *File) Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.OpenResponse) (fs.Handle,
error) {
return f, nil
}

func (f *File) ReadAll(ctx context.Context) ([]byte, error) {
f.RLock()
out := make([]byte, len(f.data))
copy(out, f.data)
f.RUnlock()

return out, nil
}

func (f *File) Write(ctx context.Context, req *fuse.WriteRequest, resp *fuse.WriteResponse) error {
f.Lock()

l := len(req.Data)
end := int(req.Offset) + l
if end > len(f.data) {
delta := end - len(f.data)
f.data = append(f.data, make([]byte, delta)...)
f.attr.Size = uint64(len(f.data))
atomic.AddInt64(&f.fs.size, int64(delta))
}
copy(f.data[req.Offset:end], req.Data)
resp.Size = l

f.Unlock()
return nil
}

func (f *File) Setattr(ctx context.Context, req *fuse.SetattrRequest,
resp *fuse.SetattrResponse) error {
f.Lock()

if req.Valid.Size() {
delta := int(req.Size) - len(f.data)
if delta > 0 {
f.data = append(f.data, make([]byte, delta)...)
} else {
f.data = f.data[0:req.Size]
}
f.attr.Size = req.Size
atomic.AddInt64(&f.fs.size, int64(delta))
}

if req.Valid.Mode() {
f.attr.Mode = req.Mode
}

if req.Valid.Atime() {
f.attr.Atime = req.Atime
}

if req.Valid.AtimeNow() {
f.attr.Atime = time.Now()
}

if req.Valid.Mtime() {
f.attr.Mtime = req.Mtime
}

if req.Valid.MtimeNow() {
f.attr.Mtime = time.Now()
}

resp.Attr = f.attr

f.Unlock()
return nil
}
Loading