Made the very basics of memfs work.
commit
e9145a324d
|
@ -4,6 +4,7 @@
|
||||||
package fuse
|
package fuse
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -121,7 +122,26 @@ type InodeID uint64
|
||||||
// are minted by the file system, the FUSE VFS layer may send a request for
|
// are minted by the file system, the FUSE VFS layer may send a request for
|
||||||
// this ID without the file system ever having referenced it in a previous
|
// this ID without the file system ever having referenced it in a previous
|
||||||
// response.
|
// response.
|
||||||
const RootInodeID InodeID = InodeID(bazilfuse.RootID)
|
const RootInodeID = 1
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// Make sure the constant above is correct. We do this at runtime rather than
|
||||||
|
// defining the constant in terms of bazilfuse.RootID for two reasons:
|
||||||
|
//
|
||||||
|
// 1. Users can more clearly see that the root ID is low and can therefore
|
||||||
|
// be used as e.g. an array index, with space reserved up to the root.
|
||||||
|
//
|
||||||
|
// 2. The constant can be untyped and can therefore more easily be used as
|
||||||
|
// an array index.
|
||||||
|
//
|
||||||
|
if RootInodeID != bazilfuse.RootID {
|
||||||
|
panic(
|
||||||
|
fmt.Sprintf(
|
||||||
|
"Oops, RootInodeID is wrong: %v vs. %v",
|
||||||
|
RootInodeID,
|
||||||
|
bazilfuse.RootID))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Attributes for a file or directory inode. Corresponds to struct inode (cf.
|
// Attributes for a file or directory inode. Corresponds to struct inode (cf.
|
||||||
// http://goo.gl/tvYyQt).
|
// http://goo.gl/tvYyQt).
|
||||||
|
|
|
@ -4,18 +4,14 @@
|
||||||
package memfs
|
package memfs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/jacobsa/fuse"
|
"github.com/jacobsa/fuse"
|
||||||
"github.com/jacobsa/fuse/fuseutil"
|
"github.com/jacobsa/fuse/fuseutil"
|
||||||
"github.com/jacobsa/gcloud/syncutil"
|
"github.com/jacobsa/gcloud/syncutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
type memDir struct {
|
type memDir struct {
|
||||||
/////////////////////////
|
|
||||||
// Constant data
|
|
||||||
/////////////////////////
|
|
||||||
|
|
||||||
inode fuse.InodeID
|
|
||||||
|
|
||||||
/////////////////////////
|
/////////////////////////
|
||||||
// Mutable state
|
// Mutable state
|
||||||
/////////////////////////
|
/////////////////////////
|
||||||
|
@ -28,5 +24,25 @@ type memDir struct {
|
||||||
// we use its indices for Dirent.Offset, which is exposed to the user who
|
// we use its indices for Dirent.Offset, which is exposed to the user who
|
||||||
// might be calling readdir in a loop while concurrently modifying the
|
// might be calling readdir in a loop while concurrently modifying the
|
||||||
// directory. Unused entries can, however, be reused.
|
// directory. Unused entries can, however, be reused.
|
||||||
|
//
|
||||||
|
// TODO(jacobsa): Add good tests exercising concurrent modifications while
|
||||||
|
// doing readdir, seekdir, etc. calls.
|
||||||
|
//
|
||||||
|
// INVARIANT: For each i, entries[i].Offset == i+1
|
||||||
entries []fuseutil.Dirent
|
entries []fuseutil.Dirent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newDir() (d *memDir) {
|
||||||
|
d = &memDir{}
|
||||||
|
d.mu = syncutil.NewInvariantMutex(d.checkInvariants)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *memDir) checkInvariants() {
|
||||||
|
for i, e := range d.entries {
|
||||||
|
if e.Offset != fuse.DirOffset(i+1) {
|
||||||
|
panic(fmt.Sprintf("Unexpected offset in entry: %v", e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -4,10 +4,13 @@
|
||||||
package memfs
|
package memfs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/jacobsa/fuse"
|
"github.com/jacobsa/fuse"
|
||||||
"github.com/jacobsa/fuse/fuseutil"
|
"github.com/jacobsa/fuse/fuseutil"
|
||||||
"github.com/jacobsa/gcloud/syncutil"
|
"github.com/jacobsa/gcloud/syncutil"
|
||||||
"github.com/jacobsa/gcsfuse/timeutil"
|
"github.com/jacobsa/gcsfuse/timeutil"
|
||||||
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
type memFS struct {
|
type memFS struct {
|
||||||
|
@ -45,5 +48,122 @@ type memFS struct {
|
||||||
// Create a file system that stores data and metadata in memory.
|
// Create a file system that stores data and metadata in memory.
|
||||||
func NewMemFS(
|
func NewMemFS(
|
||||||
clock timeutil.Clock) fuse.FileSystem {
|
clock timeutil.Clock) fuse.FileSystem {
|
||||||
panic("TODO(jacobsa): Implement NewMemFS.")
|
// Set up the basic struct.
|
||||||
|
fs := &memFS{
|
||||||
|
clock: clock,
|
||||||
|
inodes: make([]inode, fuse.RootInodeID+1),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up the root inode.
|
||||||
|
fs.inodes[fuse.RootInodeID].impl = newDir()
|
||||||
|
|
||||||
|
// Set up invariant checking.
|
||||||
|
fs.mu = syncutil.NewInvariantMutex(fs.checkInvariants)
|
||||||
|
|
||||||
|
return fs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *memFS) checkInvariants() {
|
||||||
|
// Check general inode invariants.
|
||||||
|
for i := range fs.inodes {
|
||||||
|
fs.inodes[i].checkInvariants()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check reserved inodes.
|
||||||
|
for i := 0; i < fuse.RootInodeID; i++ {
|
||||||
|
var inode *inode = &fs.inodes[i]
|
||||||
|
if inode.impl != nil {
|
||||||
|
panic(fmt.Sprintf("Non-nil impl for ID: %v", i))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the root inode.
|
||||||
|
_ = fs.inodes[fuse.RootInodeID].impl.(*memDir)
|
||||||
|
|
||||||
|
// Check inodes, building our own set of free IDs.
|
||||||
|
freeIDsEncountered := make(map[fuse.InodeID]struct{})
|
||||||
|
for i := fuse.RootInodeID + 1; i < len(fs.inodes); i++ {
|
||||||
|
var inode *inode = &fs.inodes[i]
|
||||||
|
if inode.impl == nil {
|
||||||
|
freeIDsEncountered[fuse.InodeID(i)] = struct{}{}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check fs.freeInodes.
|
||||||
|
if len(fs.freeInodes) != len(freeIDsEncountered) {
|
||||||
|
panic(
|
||||||
|
fmt.Sprintf(
|
||||||
|
"Length mismatch: %v vs. %v",
|
||||||
|
len(fs.freeInodes),
|
||||||
|
len(freeIDsEncountered)))
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, id := range fs.freeInodes {
|
||||||
|
if _, ok := freeIDsEncountered[id]; !ok {
|
||||||
|
panic(fmt.Sprintf("Unexected free inode ID: %v", id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *memFS) Init(
|
||||||
|
ctx context.Context,
|
||||||
|
req *fuse.InitRequest) (resp *fuse.InitResponse, err error) {
|
||||||
|
resp = &fuse.InitResponse{}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Panic if not a live dir.
|
||||||
|
//
|
||||||
|
// LOCKS_EXCLUDED(fs.mu)
|
||||||
|
func (fs *memFS) getDirOrDie(inodeID fuse.InodeID) (d *memDir) {
|
||||||
|
fs.mu.RLock()
|
||||||
|
defer fs.mu.RUnlock()
|
||||||
|
|
||||||
|
if inodeID >= fuse.InodeID(len(fs.inodes)) {
|
||||||
|
panic(fmt.Sprintf("Inode out of range: %v vs. %v", inodeID, len(fs.inodes)))
|
||||||
|
}
|
||||||
|
|
||||||
|
var inode *inode = &fs.inodes[inodeID]
|
||||||
|
d = inode.impl.(*memDir)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *memFS) OpenDir(
|
||||||
|
ctx context.Context,
|
||||||
|
req *fuse.OpenDirRequest) (resp *fuse.OpenDirResponse, err error) {
|
||||||
|
resp = &fuse.OpenDirResponse{}
|
||||||
|
|
||||||
|
// We don't mutate spontaneosuly, so if the VFS layer has asked for an
|
||||||
|
// inode that doesn't exist, something screwed up earlier (a lookup, a
|
||||||
|
// cache invalidation, etc.).
|
||||||
|
_ = fs.getDirOrDie(req.Inode)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *memFS) ReadDir(
|
||||||
|
ctx context.Context,
|
||||||
|
req *fuse.ReadDirRequest) (resp *fuse.ReadDirResponse, err error) {
|
||||||
|
resp = &fuse.ReadDirResponse{}
|
||||||
|
|
||||||
|
// Grab the directory.
|
||||||
|
d := fs.getDirOrDie(req.Inode)
|
||||||
|
|
||||||
|
d.mu.RLock()
|
||||||
|
defer d.mu.RUnlock()
|
||||||
|
|
||||||
|
// Return the entries requested.
|
||||||
|
for i := int(req.Offset); i < len(d.entries); i++ {
|
||||||
|
resp.Data = fuseutil.AppendDirent(resp.Data, d.entries[i])
|
||||||
|
|
||||||
|
// Trim and stop early if we've exceeded the requested size.
|
||||||
|
if len(resp.Data) > req.Size {
|
||||||
|
resp.Data = resp.Data[:req.Size]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,16 @@
|
||||||
|
|
||||||
package memfs
|
package memfs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
// Common attributes for files and directories.
|
// Common attributes for files and directories.
|
||||||
|
//
|
||||||
|
// TODO(jacobsa): Add tests for interacting with a file/directory after it has
|
||||||
|
// been unlinked, including creating a new file. Make sure we don't screw up
|
||||||
|
// and reuse the inode while it is still in use.
|
||||||
type inode struct {
|
type inode struct {
|
||||||
// The *memFile or *memDir for this inode, or nil if the inode is available
|
// The *memFile or *memDir for this inode, or nil if the inode is available
|
||||||
// for reuse.
|
// for reuse.
|
||||||
|
@ -11,3 +20,14 @@ type inode struct {
|
||||||
// INVARIANT: impl is nil, or of type *memFile or *memDir
|
// INVARIANT: impl is nil, or of type *memFile or *memDir
|
||||||
impl interface{}
|
impl interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (inode *inode) checkInvariants() {
|
||||||
|
switch inode.impl.(type) {
|
||||||
|
case nil:
|
||||||
|
case *memFile:
|
||||||
|
case *memDir:
|
||||||
|
default:
|
||||||
|
panic(
|
||||||
|
fmt.Sprintf("Unexpected inode impl type: %v", reflect.TypeOf(inode.impl)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ import (
|
||||||
"github.com/jacobsa/fuse"
|
"github.com/jacobsa/fuse"
|
||||||
"github.com/jacobsa/fuse/samples/memfs"
|
"github.com/jacobsa/fuse/samples/memfs"
|
||||||
"github.com/jacobsa/gcsfuse/timeutil"
|
"github.com/jacobsa/gcsfuse/timeutil"
|
||||||
|
. "github.com/jacobsa/oglematchers"
|
||||||
. "github.com/jacobsa/ogletest"
|
. "github.com/jacobsa/ogletest"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
@ -85,7 +86,10 @@ func (t *MemFSTest) TearDown() {
|
||||||
////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
func (t *MemFSTest) ContentsOfEmptyFileSystem() {
|
func (t *MemFSTest) ContentsOfEmptyFileSystem() {
|
||||||
AssertTrue(false, "TODO")
|
entries, err := ioutil.ReadDir(t.mfs.Dir())
|
||||||
|
|
||||||
|
AssertEq(nil, err)
|
||||||
|
ExpectThat(entries, ElementsAre())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *MemFSTest) DoesFoo() {
|
func (t *MemFSTest) DoesFoo() {
|
||||||
|
|
Loading…
Reference in New Issue