diff options
| -rw-r--r-- | cryptfs/fs.go | 2 | ||||
| -rw-r--r-- | frontend/fs.go | 13 | ||||
| -rw-r--r-- | main.go | 395 | 
3 files changed, 408 insertions, 2 deletions
diff --git a/cryptfs/fs.go b/cryptfs/fs.go index b40dcf3..9050b30 100644 --- a/cryptfs/fs.go +++ b/cryptfs/fs.go @@ -26,7 +26,7 @@ type FS struct {  	cipherBS int64  } -func New(key [16]byte) *FS { +func NewFS(key [16]byte) *FS {  	b, err := aes.NewCipher(key[:])  	if err != nil { diff --git a/frontend/fs.go b/frontend/fs.go index 464c19f..4ce3e5c 100644 --- a/frontend/fs.go +++ b/frontend/fs.go @@ -2,8 +2,19 @@ package frontend  import (  	"github.com/rfjakob/gocryptfs/cryptfs" +	"bazil.org/fuse/fs"  )  type FS struct { -	cryptfs.FS +	*cryptfs.FS +} + +func New(key [16]byte) *FS { +	return &FS { +		FS: cryptfs.NewFS(key), +	} +} + +func (fs *FS) Root() (fs.Node, error) { +	return nil, nil  } @@ -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 +}  | 
