summaryrefslogtreecommitdiff
path: root/main.go
diff options
context:
space:
mode:
Diffstat (limited to 'main.go')
-rw-r--r--main.go395
1 files changed, 395 insertions, 0 deletions
diff --git a/main.go b/main.go
new file mode 100644
index 0000000..1183fec
--- /dev/null
+++ b/main.go
@@ -0,0 +1,395 @@
+// 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"
+
+ "github.com/rfjakob/gocryptfs/frontend"
+)
+
+// 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)
+ var key [16]byte
+ filesys := frontend.New(key)
+
+ 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
+}