-
Notifications
You must be signed in to change notification settings - Fork 270
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
404 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,3 +9,4 @@ | |
|
||
/clockfs | ||
/hellofs | ||
/memfs |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
Oops, something went wrong.