Added a FileSystem.MkDir method.
Expanded memfs to support it, including tests.geesefs-0-30-9
commit
79d87d5956
|
@ -52,6 +52,16 @@ type FileSystem interface {
|
|||
ctx context.Context,
|
||||
req *ForgetInodeRequest) (*ForgetInodeResponse, error)
|
||||
|
||||
///////////////////////////////////
|
||||
// Inode creation
|
||||
///////////////////////////////////
|
||||
|
||||
// Create a directory inode as a child of an existing directory inode. The
|
||||
// kernel sends this in response to a mkdir(2) call.
|
||||
MkDir(
|
||||
ctx context.Context,
|
||||
req *MkDirRequest) (*MkDirResponse, error)
|
||||
|
||||
///////////////////////////////////
|
||||
// Directory handles
|
||||
///////////////////////////////////
|
||||
|
@ -187,36 +197,10 @@ type HandleID uint64
|
|||
// ReadDirRequest.Offset for details.
|
||||
type DirOffset uint64
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// Requests and responses
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
type InitRequest struct {
|
||||
// User and group IDs for the process that is mounting the file system.
|
||||
Uid uint32
|
||||
Gid uint32
|
||||
}
|
||||
|
||||
type InitResponse struct {
|
||||
}
|
||||
|
||||
type LookUpInodeRequest struct {
|
||||
// The ID of the directory inode to which the child belongs.
|
||||
Parent InodeID
|
||||
|
||||
// The name of the child of interest, relative to the parent. For example, in
|
||||
// this directory structure:
|
||||
//
|
||||
// foo/
|
||||
// bar/
|
||||
// baz
|
||||
//
|
||||
// the file system may receive a request to look up the child named "bar" for
|
||||
// the parent foo/.
|
||||
Name string
|
||||
}
|
||||
|
||||
type LookUpInodeResponse struct {
|
||||
// Information about a child inode within its parent directory. Shared by the
|
||||
// responses for LookUpInode, MkDir, etc. Consumed by the kernel in order to
|
||||
// set up a dcache entry.
|
||||
type ChildInodeEntry struct {
|
||||
// The ID of the child inode. The file system must ensure that the returned
|
||||
// inode ID remains valid until a later call to ForgetInode.
|
||||
Child InodeID
|
||||
|
@ -284,6 +268,39 @@ type LookUpInodeResponse struct {
|
|||
EntryExpiration time.Time
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// Requests and responses
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
type InitRequest struct {
|
||||
// User and group IDs for the process that is mounting the file system.
|
||||
Uid uint32
|
||||
Gid uint32
|
||||
}
|
||||
|
||||
type InitResponse struct {
|
||||
}
|
||||
|
||||
type LookUpInodeRequest struct {
|
||||
// The ID of the directory inode to which the child belongs.
|
||||
Parent InodeID
|
||||
|
||||
// The name of the child of interest, relative to the parent. For example, in
|
||||
// this directory structure:
|
||||
//
|
||||
// foo/
|
||||
// bar/
|
||||
// baz
|
||||
//
|
||||
// the file system may receive a request to look up the child named "bar" for
|
||||
// the parent foo/.
|
||||
Name string
|
||||
}
|
||||
|
||||
type LookUpInodeResponse struct {
|
||||
Entry ChildInodeEntry
|
||||
}
|
||||
|
||||
type GetInodeAttributesRequest struct {
|
||||
// The inode of interest.
|
||||
Inode InodeID
|
||||
|
@ -306,6 +323,19 @@ type ForgetInodeRequest struct {
|
|||
type ForgetInodeResponse struct {
|
||||
}
|
||||
|
||||
type MkDirRequest struct {
|
||||
// The ID of parent directory inode within which to create the child.
|
||||
Parent InodeID
|
||||
|
||||
// The name of the child to create, and the mode with which to create it.
|
||||
Name string
|
||||
Mode os.FileMode
|
||||
}
|
||||
|
||||
type MkDirResponse struct {
|
||||
Entry ChildInodeEntry
|
||||
}
|
||||
|
||||
type OpenDirRequest struct {
|
||||
// The ID of the inode to be opened.
|
||||
Inode InodeID
|
||||
|
|
|
@ -40,6 +40,12 @@ func (fs *NotImplementedFileSystem) ForgetInode(
|
|||
return nil, fuse.ENOSYS
|
||||
}
|
||||
|
||||
func (fs *NotImplementedFileSystem) MkDir(
|
||||
ctx context.Context,
|
||||
req *fuse.MkDirRequest) (*fuse.MkDirResponse, error) {
|
||||
return nil, fuse.ENOSYS
|
||||
}
|
||||
|
||||
func (fs *NotImplementedFileSystem) OpenDir(
|
||||
ctx context.Context,
|
||||
req *fuse.OpenDirRequest) (*fuse.OpenDirResponse, error) {
|
||||
|
|
|
@ -1,48 +0,0 @@
|
|||
// Copyright 2015 Google Inc. All Rights Reserved.
|
||||
// Author: jacobsa@google.com (Aaron Jacobs)
|
||||
|
||||
package memfs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jacobsa/fuse"
|
||||
"github.com/jacobsa/fuse/fuseutil"
|
||||
"github.com/jacobsa/gcloud/syncutil"
|
||||
)
|
||||
|
||||
type memDir struct {
|
||||
/////////////////////////
|
||||
// Mutable state
|
||||
/////////////////////////
|
||||
|
||||
mu syncutil.InvariantMutex
|
||||
|
||||
// The contents of the directory. An entry with inode zero is unused.
|
||||
//
|
||||
// This array can never be shortened, nor can its elements be moved, because
|
||||
// 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
|
||||
// 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
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
// Copyright 2015 Google Inc. All Rights Reserved.
|
||||
// Author: jacobsa@google.com (Aaron Jacobs)
|
||||
|
||||
package memfs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
"github.com/jacobsa/fuse"
|
||||
)
|
||||
|
||||
type memFile struct {
|
||||
/////////////////////////
|
||||
// Constant data
|
||||
/////////////////////////
|
||||
|
||||
inode fuse.InodeID
|
||||
|
||||
/////////////////////////
|
||||
// Mutable state
|
||||
/////////////////////////
|
||||
|
||||
mu sync.RWMutex
|
||||
|
||||
// The current contents of the file.
|
||||
contents []byte // GUARDED_BY(mu)
|
||||
}
|
||||
|
||||
// TODO(jacobsa): Add a test that various WriteAt calls with a real on-disk
|
||||
// file to verify what the behavior should be here, particularly when starting
|
||||
// a write well beyond EOF. Leave the test around for documentation purposes.
|
||||
func (f *memFile) WriteAt(p []byte, off int64) (n int, err error) {
|
||||
err = errors.New("TODO(jacobsa): Implement memFile.WriteAt.")
|
||||
return
|
||||
}
|
|
@ -5,6 +5,8 @@ package memfs
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/jacobsa/fuse"
|
||||
"github.com/jacobsa/fuse/fuseutil"
|
||||
|
@ -26,22 +28,23 @@ type memFS struct {
|
|||
// Mutable state
|
||||
/////////////////////////
|
||||
|
||||
// When acquiring this lock, the caller must hold no inode locks.
|
||||
mu syncutil.InvariantMutex
|
||||
|
||||
// The collection of all inodes that have ever been created, indexed by inode
|
||||
// ID. Some inodes are not in use if they have been unlinked, and no inode
|
||||
// with ID less than fuse.RootInodeID is ever used.
|
||||
// The collection of live inodes, indexed by ID. IDs of free inodes that may
|
||||
// be re-used have nil entries. No ID less than fuse.RootInodeID is ever used.
|
||||
//
|
||||
// INVARIANT: len(inodes) > fuse.RootInodeID
|
||||
// INVARIANT: For all i < fuse.RootInodeID, inodes[i].impl == nil
|
||||
// INVARIANT: inodes[fuse.RootInodeID].impl is of type *memDir
|
||||
inodes []inode // GUARDED_BY(mu)
|
||||
// INVARIANT: For all i < fuse.RootInodeID, inodes[i] == nil
|
||||
// INVARIANT: inodes[fuse.RootInodeID] != nil
|
||||
// INVARIANT: inodes[fuse.RootInodeID].dir is true
|
||||
inodes []*inode // GUARDED_BY(mu)
|
||||
|
||||
// A list of inode IDs within inodes available for reuse, not including the
|
||||
// reserved IDs less than fuse.RootInodeID.
|
||||
//
|
||||
// INVARIANT: This is all and only indices i of inodes such that i >
|
||||
// fuse.RootInodeID and inodes[i].impl == nil
|
||||
// INVARIANT: This is all and only indices i of 'inodes' such that i >
|
||||
// fuse.RootInodeID and inodes[i] == nil
|
||||
freeInodes []fuse.InodeID // GUARDED_BY(mu)
|
||||
}
|
||||
|
||||
|
@ -51,11 +54,15 @@ func NewMemFS(
|
|||
// Set up the basic struct.
|
||||
fs := &memFS{
|
||||
clock: clock,
|
||||
inodes: make([]inode, fuse.RootInodeID+1),
|
||||
inodes: make([]*inode, fuse.RootInodeID+1),
|
||||
}
|
||||
|
||||
// Set up the root inode.
|
||||
fs.inodes[fuse.RootInodeID].impl = newDir()
|
||||
rootAttrs := fuse.InodeAttributes{
|
||||
Mode: 0777 | os.ModeDir,
|
||||
}
|
||||
|
||||
fs.inodes[fuse.RootInodeID] = newInode(rootAttrs)
|
||||
|
||||
// Set up invariant checking.
|
||||
fs.mu = syncutil.NewInvariantMutex(fs.checkInvariants)
|
||||
|
@ -64,27 +71,23 @@ func NewMemFS(
|
|||
}
|
||||
|
||||
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))
|
||||
if fs.inodes[i] != nil {
|
||||
panic(fmt.Sprintf("Non-nil inode for ID: %v", i))
|
||||
}
|
||||
}
|
||||
|
||||
// Check the root inode.
|
||||
_ = fs.inodes[fuse.RootInodeID].impl.(*memDir)
|
||||
if !fs.inodes[fuse.RootInodeID].dir {
|
||||
panic("Expected root to be a directory.")
|
||||
}
|
||||
|
||||
// Check inodes, building our own set of free IDs.
|
||||
// Build our own list 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 {
|
||||
inode := fs.inodes[i]
|
||||
if inode == nil {
|
||||
freeIDsEncountered[fuse.InodeID(i)] = struct{}{}
|
||||
continue
|
||||
}
|
||||
|
@ -113,19 +116,154 @@ func (fs *memFS) Init(
|
|||
return
|
||||
}
|
||||
|
||||
// Panic if not a live dir.
|
||||
// Find the given inode and return it with its lock held. Panic if it doesn't
|
||||
// exist.
|
||||
//
|
||||
// LOCKS_EXCLUDED(fs.mu)
|
||||
func (fs *memFS) getDirOrDie(inodeID fuse.InodeID) (d *memDir) {
|
||||
// SHARED_LOCKS_REQUIRED(fs.mu)
|
||||
// EXCLUSIVE_LOCK_FUNCTION(inode.mu)
|
||||
func (fs *memFS) getInodeForModifyingOrDie(id fuse.InodeID) (inode *inode) {
|
||||
inode = fs.inodes[id]
|
||||
if inode == nil {
|
||||
panic(fmt.Sprintf("Unknown inode: %v", id))
|
||||
}
|
||||
|
||||
inode.mu.Lock()
|
||||
return
|
||||
}
|
||||
|
||||
// Find the given inode and return it with its lock held for reading. Panic if
|
||||
// it doesn't exist.
|
||||
//
|
||||
// SHARED_LOCKS_REQUIRED(fs.mu)
|
||||
// SHARED_LOCK_FUNCTION(inode.mu)
|
||||
func (fs *memFS) getInodeForReadingOrDie(id fuse.InodeID) (inode *inode) {
|
||||
inode = fs.inodes[id]
|
||||
if inode == nil {
|
||||
panic(fmt.Sprintf("Unknown inode: %v", id))
|
||||
}
|
||||
|
||||
inode.mu.RLock()
|
||||
return
|
||||
}
|
||||
|
||||
// Allocate a new inode, assigning it an ID that is not in use. Return it with
|
||||
// its lock held.
|
||||
//
|
||||
// EXCLUSIVE_LOCKS_REQUIRED(fs.mu)
|
||||
// EXCLUSIVE_LOCK_FUNCTION(inode.mu)
|
||||
func (fs *memFS) allocateInode(
|
||||
attrs fuse.InodeAttributes) (id fuse.InodeID, inode *inode) {
|
||||
// Create and lock the inode.
|
||||
inode = newInode(attrs)
|
||||
inode.mu.Lock()
|
||||
|
||||
// Re-use a free ID if possible. Otherwise mint a new one.
|
||||
numFree := len(fs.freeInodes)
|
||||
if numFree != 0 {
|
||||
id = fs.freeInodes[numFree-1]
|
||||
fs.freeInodes = fs.freeInodes[:numFree-1]
|
||||
fs.inodes[id] = inode
|
||||
} else {
|
||||
id = fuse.InodeID(len(fs.inodes))
|
||||
fs.inodes = append(fs.inodes, inode)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (fs *memFS) LookUpInode(
|
||||
ctx context.Context,
|
||||
req *fuse.LookUpInodeRequest) (resp *fuse.LookUpInodeResponse, err error) {
|
||||
resp = &fuse.LookUpInodeResponse{}
|
||||
|
||||
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)))
|
||||
// Grab the parent directory.
|
||||
inode := fs.getInodeForReadingOrDie(req.Parent)
|
||||
defer inode.mu.RUnlock()
|
||||
|
||||
// Does the directory have an entry with the given name?
|
||||
childID, ok := inode.LookUpChild(req.Name)
|
||||
if !ok {
|
||||
err = fuse.ENOENT
|
||||
return
|
||||
}
|
||||
|
||||
var inode *inode = &fs.inodes[inodeID]
|
||||
d = inode.impl.(*memDir)
|
||||
// Grab the child.
|
||||
child := fs.getInodeForReadingOrDie(childID)
|
||||
defer child.mu.RUnlock()
|
||||
|
||||
// Fill in the response.
|
||||
resp.Entry.Child = childID
|
||||
resp.Entry.Attributes = child.attributes
|
||||
|
||||
// We don't spontaneously mutate, so the kernel can cache as long as it wants
|
||||
// (since it also handles invalidation).
|
||||
resp.Entry.AttributesExpiration = fs.clock.Now().Add(365 * 24 * time.Hour)
|
||||
resp.Entry.EntryExpiration = resp.Entry.EntryExpiration
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (fs *memFS) GetInodeAttributes(
|
||||
ctx context.Context,
|
||||
req *fuse.GetInodeAttributesRequest) (
|
||||
resp *fuse.GetInodeAttributesResponse, err error) {
|
||||
resp = &fuse.GetInodeAttributesResponse{}
|
||||
|
||||
fs.mu.RLock()
|
||||
defer fs.mu.RUnlock()
|
||||
|
||||
// Grab the inode.
|
||||
inode := fs.getInodeForReadingOrDie(req.Inode)
|
||||
defer inode.mu.RUnlock()
|
||||
|
||||
// Fill in the response.
|
||||
resp.Attributes = inode.attributes
|
||||
|
||||
// We don't spontaneously mutate, so the kernel can cache as long as it wants
|
||||
// (since it also handles invalidation).
|
||||
resp.AttributesExpiration = fs.clock.Now().Add(365 * 24 * time.Hour)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (fs *memFS) MkDir(
|
||||
ctx context.Context,
|
||||
req *fuse.MkDirRequest) (resp *fuse.MkDirResponse, err error) {
|
||||
resp = &fuse.MkDirResponse{}
|
||||
|
||||
fs.mu.Lock()
|
||||
defer fs.mu.Unlock()
|
||||
|
||||
// Grab the parent, which we will update shortly.
|
||||
parent := fs.getInodeForModifyingOrDie(req.Parent)
|
||||
defer parent.mu.Unlock()
|
||||
|
||||
// Allocate a child.
|
||||
now := fs.clock.Now()
|
||||
childAttrs := fuse.InodeAttributes{
|
||||
Mode: req.Mode,
|
||||
Atime: now,
|
||||
Mtime: now,
|
||||
Crtime: now,
|
||||
}
|
||||
|
||||
childID, child := fs.allocateInode(childAttrs)
|
||||
defer child.mu.Unlock()
|
||||
|
||||
// Add an entry in the parent.
|
||||
parent.AddChild(childID, req.Name, fuseutil.DT_Directory)
|
||||
|
||||
// Fill in the response.
|
||||
resp.Entry.Child = childID
|
||||
resp.Entry.Attributes = child.attributes
|
||||
|
||||
// We don't spontaneously mutate, so the kernel can cache as long as it wants
|
||||
// (since it also handles invalidation).
|
||||
resp.Entry.AttributesExpiration = fs.clock.Now().Add(365 * 24 * time.Hour)
|
||||
resp.Entry.EntryExpiration = resp.Entry.EntryExpiration
|
||||
|
||||
return
|
||||
}
|
||||
|
@ -135,10 +273,18 @@ func (fs *memFS) OpenDir(
|
|||
req *fuse.OpenDirRequest) (resp *fuse.OpenDirResponse, err error) {
|
||||
resp = &fuse.OpenDirResponse{}
|
||||
|
||||
fs.mu.RLock()
|
||||
defer fs.mu.RUnlock()
|
||||
|
||||
// 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)
|
||||
inode := fs.getInodeForReadingOrDie(req.Inode)
|
||||
defer inode.mu.RUnlock()
|
||||
|
||||
if !inode.dir {
|
||||
panic("Found non-dir.")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
@ -148,21 +294,18 @@ func (fs *memFS) ReadDir(
|
|||
req *fuse.ReadDirRequest) (resp *fuse.ReadDirResponse, err error) {
|
||||
resp = &fuse.ReadDirResponse{}
|
||||
|
||||
fs.mu.RLock()
|
||||
defer fs.mu.RUnlock()
|
||||
|
||||
// Grab the directory.
|
||||
d := fs.getDirOrDie(req.Inode)
|
||||
inode := fs.getInodeForReadingOrDie(req.Inode)
|
||||
defer inode.mu.RUnlock()
|
||||
|
||||
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
|
||||
}
|
||||
// Serve the request.
|
||||
resp.Data, err = inode.ReadDir(int(req.Offset), req.Size)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("inode.ReadDir: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
|
|
|
@ -5,29 +5,169 @@ package memfs
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"os"
|
||||
|
||||
"github.com/jacobsa/fuse"
|
||||
"github.com/jacobsa/fuse/fuseutil"
|
||||
"github.com/jacobsa/gcloud/syncutil"
|
||||
)
|
||||
|
||||
// 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.
|
||||
// and reuse an inode ID while it is still in use.
|
||||
type inode struct {
|
||||
// The *memFile or *memDir for this inode, or nil if the inode is available
|
||||
// for reuse.
|
||||
/////////////////////////
|
||||
// Constant data
|
||||
/////////////////////////
|
||||
|
||||
// Is this a directory? If not, it is a file.
|
||||
dir bool
|
||||
|
||||
/////////////////////////
|
||||
// Mutable state
|
||||
/////////////////////////
|
||||
|
||||
mu syncutil.InvariantMutex
|
||||
|
||||
// The current attributes of this inode.
|
||||
//
|
||||
// INVARIANT: impl is nil, or of type *memFile or *memDir
|
||||
impl interface{}
|
||||
// INVARIANT: No non-permission mode bits are set besides os.ModeDir
|
||||
// INVARIANT: If dir, then os.ModeDir is set
|
||||
// INVARIANT: If !dir, then os.ModeDir is not set
|
||||
attributes fuse.InodeAttributes // GUARDED_BY(mu)
|
||||
|
||||
// For directories, entries describing the children of the directory.
|
||||
//
|
||||
// This array can never be shortened, nor can its elements be moved, because
|
||||
// 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
|
||||
// directory. Unused entries can, however, be reused.
|
||||
//
|
||||
// TODO(jacobsa): Add good tests exercising concurrent modifications while
|
||||
// doing readdir, seekdir, etc. calls.
|
||||
//
|
||||
// INVARIANT: If dir is false, this is nil.
|
||||
// INVARIANT: For each i, entries[i].Offset == i+1
|
||||
// INVARIANT: Contains no duplicate names.
|
||||
entries []fuseutil.Dirent // GUARDED_BY(mu)
|
||||
|
||||
// For files, the current contents of the file.
|
||||
//
|
||||
// INVARIANT: If dir is true, this is nil.
|
||||
contents []byte // GUARDED_BY(mu)
|
||||
}
|
||||
|
||||
func newInode(attrs fuse.InodeAttributes) (in *inode) {
|
||||
in = &inode{
|
||||
dir: (attrs.Mode&os.ModeDir != 0),
|
||||
attributes: attrs,
|
||||
}
|
||||
|
||||
in.mu = syncutil.NewInvariantMutex(in.checkInvariants)
|
||||
return
|
||||
}
|
||||
|
||||
func (inode *inode) checkInvariants() {
|
||||
switch inode.impl.(type) {
|
||||
case nil:
|
||||
case *memFile:
|
||||
case *memDir:
|
||||
default:
|
||||
// No non-permission mode bits should be set besides os.ModeDir.
|
||||
if inode.attributes.Mode & ^(os.ModePerm|os.ModeDir) != 0 {
|
||||
panic(fmt.Sprintf("Unexpected mode: %v", inode.attributes.Mode))
|
||||
}
|
||||
|
||||
// Check os.ModeDir.
|
||||
if inode.dir != (inode.attributes.Mode&os.ModeDir == os.ModeDir) {
|
||||
panic(
|
||||
fmt.Sprintf("Unexpected inode impl type: %v", reflect.TypeOf(inode.impl)))
|
||||
fmt.Sprintf(
|
||||
"Unexpected mode: %v, dir: %v",
|
||||
inode.attributes.Mode,
|
||||
inode.dir))
|
||||
}
|
||||
|
||||
// Check directory-specific stuff.
|
||||
if inode.dir {
|
||||
if inode.contents != nil {
|
||||
panic("Non-nil contents in a directory.")
|
||||
}
|
||||
|
||||
childNames := make(map[string]struct{})
|
||||
for i, e := range inode.entries {
|
||||
if e.Offset != fuse.DirOffset(i+1) {
|
||||
panic(fmt.Sprintf("Unexpected offset: %v", e.Offset))
|
||||
}
|
||||
|
||||
if _, ok := childNames[e.Name]; ok {
|
||||
panic(fmt.Sprintf("Duplicate name: %s", e.Name))
|
||||
}
|
||||
|
||||
childNames[e.Name] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
// Check file-specific stuff.
|
||||
if !inode.dir {
|
||||
if inode.entries != nil {
|
||||
panic("Non-nil entries in a file.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find an entry for the given child name and return its inode ID.
|
||||
//
|
||||
// REQUIRES: inode.dir
|
||||
// SHARED_LOCKS_REQUIRED(inode.mu)
|
||||
func (inode *inode) LookUpChild(name string) (id fuse.InodeID, ok bool) {
|
||||
if !inode.dir {
|
||||
panic("LookUpChild called on non-directory.")
|
||||
}
|
||||
|
||||
for _, e := range inode.entries {
|
||||
if e.Name == name {
|
||||
id = e.Inode
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Add an entry for a child.
|
||||
//
|
||||
// REQUIRES: inode.dir
|
||||
// EXCLUSIVE_LOCKS_REQUIRED(inode.mu)
|
||||
func (inode *inode) AddChild(
|
||||
id fuse.InodeID,
|
||||
name string,
|
||||
dt fuseutil.DirentType) {
|
||||
e := fuseutil.Dirent{
|
||||
Offset: fuse.DirOffset(len(inode.entries) + 1),
|
||||
Inode: id,
|
||||
Name: name,
|
||||
Type: dt,
|
||||
}
|
||||
|
||||
inode.entries = append(inode.entries, e)
|
||||
}
|
||||
|
||||
// Serve a ReadDir request.
|
||||
//
|
||||
// REQUIRED: inode.dir
|
||||
// SHARED_LOCKS_REQUIRED(inode.mu)
|
||||
func (inode *inode) ReadDir(offset int, size int) (data []byte, err error) {
|
||||
if !inode.dir {
|
||||
panic("ReadDir called on non-directory.")
|
||||
}
|
||||
|
||||
for i := offset; i < len(inode.entries); i++ {
|
||||
data = fuseutil.AppendDirent(data, inode.entries[i])
|
||||
|
||||
// Trim and stop early if we've exceeded the requested size.
|
||||
if len(data) > size {
|
||||
data = data[:size]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
|
|
@ -6,6 +6,8 @@ package memfs_test
|
|||
import (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
@ -93,11 +95,75 @@ func (t *MemFSTest) ContentsOfEmptyFileSystem() {
|
|||
}
|
||||
|
||||
func (t *MemFSTest) Mkdir() {
|
||||
AssertTrue(false, "TODO")
|
||||
var err error
|
||||
var fi os.FileInfo
|
||||
dirName := path.Join(t.mfs.Dir(), "dir")
|
||||
|
||||
// Create a directory within the root.
|
||||
createTime := t.clock.Now()
|
||||
err = os.Mkdir(dirName, 0754)
|
||||
AssertEq(nil, err)
|
||||
|
||||
// Simulate time proceeding.
|
||||
t.clock.AdvanceTime(time.Second)
|
||||
|
||||
// Stat the directory.
|
||||
fi, err = os.Stat(dirName)
|
||||
|
||||
AssertEq(nil, err)
|
||||
ExpectEq("dir", fi.Name())
|
||||
ExpectEq(0, fi.Size())
|
||||
ExpectEq(os.ModeDir|0754, fi.Mode())
|
||||
ExpectEq(0, fi.ModTime().Sub(createTime))
|
||||
ExpectTrue(fi.IsDir())
|
||||
|
||||
// Read the directory.
|
||||
entries, err := ioutil.ReadDir(dirName)
|
||||
|
||||
AssertEq(nil, err)
|
||||
ExpectThat(entries, ElementsAre())
|
||||
}
|
||||
|
||||
func (t *MemFSTest) Mkdir_AlreadyExists() {
|
||||
AssertTrue(false, "TODO")
|
||||
var err error
|
||||
dirName := path.Join(t.mfs.Dir(), "dir")
|
||||
|
||||
// Create the directory once.
|
||||
err = os.Mkdir(dirName, 0754)
|
||||
AssertEq(nil, err)
|
||||
|
||||
// Attempt to create it again.
|
||||
err = os.Mkdir(dirName, 0754)
|
||||
|
||||
AssertNe(nil, err)
|
||||
ExpectThat(err, Error(HasSubstr("exists")))
|
||||
}
|
||||
|
||||
func (t *MemFSTest) Mkdir_IntermediateIsFile() {
|
||||
var err error
|
||||
|
||||
// Create a file.
|
||||
fileName := path.Join(t.mfs.Dir(), "foo")
|
||||
err = ioutil.WriteFile(fileName, []byte{}, 0700)
|
||||
AssertEq(nil, err)
|
||||
|
||||
// Attempt to create a directory within the file.
|
||||
dirName := path.Join(fileName, "dir")
|
||||
err = os.Mkdir(dirName, 0754)
|
||||
|
||||
AssertNe(nil, err)
|
||||
ExpectThat(err, Error(HasSubstr("TODO")))
|
||||
}
|
||||
|
||||
func (t *MemFSTest) Mkdir_IntermediateIsNonExistent() {
|
||||
var err error
|
||||
|
||||
// Attempt to create a sub-directory of a non-existent sub-directory.
|
||||
dirName := path.Join(t.mfs.Dir(), "foo/dir")
|
||||
err = os.Mkdir(dirName, 0754)
|
||||
|
||||
AssertNe(nil, err)
|
||||
ExpectThat(err, Error(HasSubstr("no such file or directory")))
|
||||
}
|
||||
|
||||
func (t *MemFSTest) CreateNewFile_InRoot() {
|
||||
|
|
71
server.go
71
server.go
|
@ -32,6 +32,17 @@ func newServer(fs FileSystem) (s *server, err error) {
|
|||
return
|
||||
}
|
||||
|
||||
func convertChildInodeEntry(
|
||||
clock timeutil.Clock,
|
||||
in *ChildInodeEntry,
|
||||
out *bazilfuse.LookupResponse) {
|
||||
out.Node = bazilfuse.NodeID(in.Child)
|
||||
out.Generation = uint64(in.Generation)
|
||||
out.Attr = convertAttributes(in.Child, in.Attributes)
|
||||
out.AttrValid = in.AttributesExpiration.Sub(clock.Now())
|
||||
out.EntryValid = in.EntryExpiration.Sub(clock.Now())
|
||||
}
|
||||
|
||||
// Serve the fuse connection by repeatedly reading requests from the supplied
|
||||
// FUSE connection, responding as dictated by the file system. Return when the
|
||||
// connection is closed or an unexpected error occurs.
|
||||
|
@ -78,14 +89,14 @@ func (s *server) handleFuseRequest(fuseReq bazilfuse.Request) {
|
|||
// Call the file system.
|
||||
_, err := s.fs.Init(ctx, req)
|
||||
if err != nil {
|
||||
s.logger.Print("Responding:", err)
|
||||
s.logger.Println("Responding:", err)
|
||||
typed.RespondError(err)
|
||||
return
|
||||
}
|
||||
|
||||
// Convert the response.
|
||||
fuseResp := &bazilfuse.InitResponse{}
|
||||
s.logger.Print("Responding:", fuseResp)
|
||||
s.logger.Println("Responding:", fuseResp)
|
||||
typed.Respond(fuseResp)
|
||||
|
||||
case *bazilfuse.StatfsRequest:
|
||||
|
@ -106,21 +117,16 @@ func (s *server) handleFuseRequest(fuseReq bazilfuse.Request) {
|
|||
// Call the file system.
|
||||
resp, err := s.fs.LookUpInode(ctx, req)
|
||||
if err != nil {
|
||||
s.logger.Print("Responding:", err)
|
||||
s.logger.Println("Responding:", err)
|
||||
typed.RespondError(err)
|
||||
return
|
||||
}
|
||||
|
||||
// Convert the response.
|
||||
fuseResp := &bazilfuse.LookupResponse{
|
||||
Node: bazilfuse.NodeID(resp.Child),
|
||||
Generation: uint64(resp.Generation),
|
||||
Attr: convertAttributes(resp.Child, resp.Attributes),
|
||||
AttrValid: resp.AttributesExpiration.Sub(s.clock.Now()),
|
||||
EntryValid: resp.EntryExpiration.Sub(s.clock.Now()),
|
||||
}
|
||||
fuseResp := &bazilfuse.LookupResponse{}
|
||||
convertChildInodeEntry(s.clock, &resp.Entry, fuseResp)
|
||||
|
||||
s.logger.Print("Responding:", fuseResp)
|
||||
s.logger.Println("Responding:", fuseResp)
|
||||
typed.Respond(fuseResp)
|
||||
|
||||
case *bazilfuse.GetattrRequest:
|
||||
|
@ -132,7 +138,7 @@ func (s *server) handleFuseRequest(fuseReq bazilfuse.Request) {
|
|||
// Call the file system.
|
||||
resp, err := s.fs.GetInodeAttributes(ctx, req)
|
||||
if err != nil {
|
||||
s.logger.Print("Responding:", err)
|
||||
s.logger.Println("Responding:", err)
|
||||
typed.RespondError(err)
|
||||
return
|
||||
}
|
||||
|
@ -143,7 +149,30 @@ func (s *server) handleFuseRequest(fuseReq bazilfuse.Request) {
|
|||
AttrValid: resp.AttributesExpiration.Sub(s.clock.Now()),
|
||||
}
|
||||
|
||||
s.logger.Print("Responding:", fuseResp)
|
||||
s.logger.Println("Responding:", fuseResp)
|
||||
typed.Respond(fuseResp)
|
||||
|
||||
case *bazilfuse.MkdirRequest:
|
||||
// Convert the request.
|
||||
req := &MkDirRequest{
|
||||
Parent: InodeID(typed.Header.Node),
|
||||
Name: typed.Name,
|
||||
Mode: typed.Mode,
|
||||
}
|
||||
|
||||
// Call the file system.
|
||||
resp, err := s.fs.MkDir(ctx, req)
|
||||
if err != nil {
|
||||
s.logger.Println("Responding:", err)
|
||||
typed.RespondError(err)
|
||||
return
|
||||
}
|
||||
|
||||
// Convert the response.
|
||||
fuseResp := &bazilfuse.MkdirResponse{}
|
||||
convertChildInodeEntry(s.clock, &resp.Entry, &fuseResp.LookupResponse)
|
||||
|
||||
s.logger.Println("Responding:", fuseResp)
|
||||
typed.Respond(fuseResp)
|
||||
|
||||
case *bazilfuse.OpenRequest:
|
||||
|
@ -158,7 +187,7 @@ func (s *server) handleFuseRequest(fuseReq bazilfuse.Request) {
|
|||
// Call the file system.
|
||||
resp, err := s.fs.OpenDir(ctx, req)
|
||||
if err != nil {
|
||||
s.logger.Print("Responding:", err)
|
||||
s.logger.Println("Responding:", err)
|
||||
typed.RespondError(err)
|
||||
return
|
||||
}
|
||||
|
@ -168,7 +197,7 @@ func (s *server) handleFuseRequest(fuseReq bazilfuse.Request) {
|
|||
Handle: bazilfuse.HandleID(resp.Handle),
|
||||
}
|
||||
|
||||
s.logger.Print("Responding:", fuseResp)
|
||||
s.logger.Println("Responding:", fuseResp)
|
||||
typed.Respond(fuseResp)
|
||||
} else {
|
||||
// Convert the request.
|
||||
|
@ -180,7 +209,7 @@ func (s *server) handleFuseRequest(fuseReq bazilfuse.Request) {
|
|||
// Call the file system.
|
||||
resp, err := s.fs.OpenFile(ctx, req)
|
||||
if err != nil {
|
||||
s.logger.Print("Responding:", err)
|
||||
s.logger.Println("Responding:", err)
|
||||
typed.RespondError(err)
|
||||
return
|
||||
}
|
||||
|
@ -190,7 +219,7 @@ func (s *server) handleFuseRequest(fuseReq bazilfuse.Request) {
|
|||
Handle: bazilfuse.HandleID(resp.Handle),
|
||||
}
|
||||
|
||||
s.logger.Print("Responding:", fuseResp)
|
||||
s.logger.Println("Responding:", fuseResp)
|
||||
typed.Respond(fuseResp)
|
||||
}
|
||||
|
||||
|
@ -208,7 +237,7 @@ func (s *server) handleFuseRequest(fuseReq bazilfuse.Request) {
|
|||
// Call the file system.
|
||||
resp, err := s.fs.ReadDir(ctx, req)
|
||||
if err != nil {
|
||||
s.logger.Print("Responding:", err)
|
||||
s.logger.Println("Responding:", err)
|
||||
typed.RespondError(err)
|
||||
return
|
||||
}
|
||||
|
@ -218,7 +247,7 @@ func (s *server) handleFuseRequest(fuseReq bazilfuse.Request) {
|
|||
Data: resp.Data,
|
||||
}
|
||||
|
||||
s.logger.Print("Responding:", fuseResp)
|
||||
s.logger.Println("Responding:", fuseResp)
|
||||
typed.Respond(fuseResp)
|
||||
} else {
|
||||
// Convert the request.
|
||||
|
@ -232,7 +261,7 @@ func (s *server) handleFuseRequest(fuseReq bazilfuse.Request) {
|
|||
// Call the file system.
|
||||
resp, err := s.fs.ReadFile(ctx, req)
|
||||
if err != nil {
|
||||
s.logger.Print("Responding:", err)
|
||||
s.logger.Println("Responding:", err)
|
||||
typed.RespondError(err)
|
||||
return
|
||||
}
|
||||
|
@ -242,7 +271,7 @@ func (s *server) handleFuseRequest(fuseReq bazilfuse.Request) {
|
|||
Data: resp.Data,
|
||||
}
|
||||
|
||||
s.logger.Print("Responding:", fuseResp)
|
||||
s.logger.Println("Responding:", fuseResp)
|
||||
typed.Respond(fuseResp)
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue