554 lines
14 KiB
Go
554 lines
14 KiB
Go
// Copyright 2015 Google Inc. All Rights Reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package memfs
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"time"
|
|
|
|
"github.com/jacobsa/fuse"
|
|
"github.com/jacobsa/fuse/fuseutil"
|
|
"github.com/jacobsa/gcloud/syncutil"
|
|
"github.com/jacobsa/gcsfuse/timeutil"
|
|
"golang.org/x/net/context"
|
|
)
|
|
|
|
type memFS struct {
|
|
fuseutil.NotImplementedFileSystem
|
|
|
|
/////////////////////////
|
|
// Dependencies
|
|
/////////////////////////
|
|
|
|
clock timeutil.Clock
|
|
|
|
/////////////////////////
|
|
// Mutable state
|
|
/////////////////////////
|
|
|
|
// When acquiring this lock, the caller must hold no inode locks.
|
|
mu syncutil.InvariantMutex
|
|
|
|
// 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] == 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] == nil
|
|
freeInodes []fuse.InodeID // GUARDED_BY(mu)
|
|
}
|
|
|
|
// Create a file system that stores data and metadata in memory.
|
|
//
|
|
// The supplied UID/GID pair will own the root inode. This file system does no
|
|
// permissions checking, and should therefore be mounted with the
|
|
// default_permissions option.
|
|
func NewMemFS(
|
|
uid uint32,
|
|
gid uint32,
|
|
clock timeutil.Clock) fuse.FileSystem {
|
|
// Set up the basic struct.
|
|
fs := &memFS{
|
|
clock: clock,
|
|
inodes: make([]*inode, fuse.RootInodeID+1),
|
|
}
|
|
|
|
// Set up the root inode.
|
|
rootAttrs := fuse.InodeAttributes{
|
|
Mode: 0700 | os.ModeDir,
|
|
Uid: uid,
|
|
Gid: gid,
|
|
}
|
|
|
|
fs.inodes[fuse.RootInodeID] = newInode(clock, rootAttrs)
|
|
|
|
// Set up invariant checking.
|
|
fs.mu = syncutil.NewInvariantMutex(fs.checkInvariants)
|
|
|
|
return fs
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
// Helpers
|
|
////////////////////////////////////////////////////////////////////////
|
|
|
|
func (fs *memFS) checkInvariants() {
|
|
// Check reserved inodes.
|
|
for i := 0; i < fuse.RootInodeID; i++ {
|
|
if fs.inodes[i] != nil {
|
|
panic(fmt.Sprintf("Non-nil inode for ID: %v", i))
|
|
}
|
|
}
|
|
|
|
// Check the root inode.
|
|
if !fs.inodes[fuse.RootInodeID].dir {
|
|
panic("Expected root to be a directory.")
|
|
}
|
|
|
|
// Build our own list of free IDs.
|
|
freeIDsEncountered := make(map[fuse.InodeID]struct{})
|
|
for i := fuse.RootInodeID + 1; i < len(fs.inodes); i++ {
|
|
inode := fs.inodes[i]
|
|
if inode == 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))
|
|
}
|
|
}
|
|
}
|
|
|
|
// Find the given inode and return it with its lock held. Panic if it doesn't
|
|
// exist.
|
|
//
|
|
// 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(fs.clock, 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
|
|
}
|
|
|
|
// EXCLUSIVE_LOCKS_REQUIRED(fs.mu)
|
|
func (fs *memFS) deallocateInode(id fuse.InodeID) {
|
|
fs.freeInodes = append(fs.freeInodes, id)
|
|
fs.inodes[id] = nil
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
// FileSystem methods
|
|
////////////////////////////////////////////////////////////////////////
|
|
|
|
func (fs *memFS) Init(
|
|
ctx context.Context,
|
|
req *fuse.InitRequest) (resp *fuse.InitResponse, err error) {
|
|
resp = &fuse.InitResponse{}
|
|
|
|
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()
|
|
|
|
// 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
|
|
}
|
|
|
|
// 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) SetInodeAttributes(
|
|
ctx context.Context,
|
|
req *fuse.SetInodeAttributesRequest) (
|
|
resp *fuse.SetInodeAttributesResponse, err error) {
|
|
resp = &fuse.SetInodeAttributesResponse{}
|
|
|
|
fs.mu.RLock()
|
|
defer fs.mu.RUnlock()
|
|
|
|
// Grab the inode.
|
|
inode := fs.getInodeForModifyingOrDie(req.Inode)
|
|
defer inode.mu.Unlock()
|
|
|
|
// Handle the request.
|
|
inode.SetAttributes(req.Size, req.Mode, req.Mtime)
|
|
|
|
// 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()
|
|
|
|
// Set up attributes from the child, using the credentials of the calling
|
|
// process as owner (matching inode_init_owner, cf. http://goo.gl/5qavg8).
|
|
childAttrs := fuse.InodeAttributes{
|
|
Mode: req.Mode,
|
|
Uid: req.Header.Uid,
|
|
Gid: req.Header.Gid,
|
|
}
|
|
|
|
// Allocate a child.
|
|
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
|
|
}
|
|
|
|
func (fs *memFS) CreateFile(
|
|
ctx context.Context,
|
|
req *fuse.CreateFileRequest) (resp *fuse.CreateFileResponse, err error) {
|
|
resp = &fuse.CreateFileResponse{}
|
|
|
|
fs.mu.Lock()
|
|
defer fs.mu.Unlock()
|
|
|
|
// Grab the parent, which we will update shortly.
|
|
parent := fs.getInodeForModifyingOrDie(req.Parent)
|
|
defer parent.mu.Unlock()
|
|
|
|
// Set up attributes from the child, using the credentials of the calling
|
|
// process as owner (matching inode_init_owner, cf. http://goo.gl/5qavg8).
|
|
now := fs.clock.Now()
|
|
childAttrs := fuse.InodeAttributes{
|
|
Mode: req.Mode,
|
|
Atime: now,
|
|
Mtime: now,
|
|
Ctime: now,
|
|
Crtime: now,
|
|
Uid: req.Header.Uid,
|
|
Gid: req.Header.Gid,
|
|
}
|
|
|
|
// Allocate a child.
|
|
childID, child := fs.allocateInode(childAttrs)
|
|
defer child.mu.Unlock()
|
|
|
|
// Add an entry in the parent.
|
|
parent.AddChild(childID, req.Name, fuseutil.DT_File)
|
|
|
|
// Fill in the response entry.
|
|
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
|
|
|
|
// We have nothing interesting to put in the Handle field.
|
|
|
|
return
|
|
}
|
|
|
|
func (fs *memFS) RmDir(
|
|
ctx context.Context,
|
|
req *fuse.RmDirRequest) (resp *fuse.RmDirResponse, err error) {
|
|
resp = &fuse.RmDirResponse{}
|
|
|
|
fs.mu.Lock()
|
|
defer fs.mu.Unlock()
|
|
|
|
// Grab the parent, which we will update shortly.
|
|
parent := fs.getInodeForModifyingOrDie(req.Parent)
|
|
defer parent.mu.Unlock()
|
|
|
|
// Find the child within the parent.
|
|
childID, ok := parent.LookUpChild(req.Name)
|
|
if !ok {
|
|
err = fuse.ENOENT
|
|
return
|
|
}
|
|
|
|
// Grab the child.
|
|
child := fs.getInodeForModifyingOrDie(childID)
|
|
defer child.mu.Unlock()
|
|
|
|
// Make sure the child is empty.
|
|
if child.Len() != 0 {
|
|
err = fuse.ENOTEMPTY
|
|
return
|
|
}
|
|
|
|
// Remove the entry within the parent.
|
|
parent.RemoveChild(req.Name)
|
|
|
|
// Mark the child as unlinked.
|
|
child.linkCount--
|
|
|
|
return
|
|
}
|
|
|
|
func (fs *memFS) Unlink(
|
|
ctx context.Context,
|
|
req *fuse.UnlinkRequest) (resp *fuse.UnlinkResponse, err error) {
|
|
resp = &fuse.UnlinkResponse{}
|
|
|
|
fs.mu.Lock()
|
|
defer fs.mu.Unlock()
|
|
|
|
// Grab the parent, which we will update shortly.
|
|
parent := fs.getInodeForModifyingOrDie(req.Parent)
|
|
defer parent.mu.Unlock()
|
|
|
|
// Find the child within the parent.
|
|
childID, ok := parent.LookUpChild(req.Name)
|
|
if !ok {
|
|
err = fuse.ENOENT
|
|
return
|
|
}
|
|
|
|
// Grab the child.
|
|
child := fs.getInodeForModifyingOrDie(childID)
|
|
defer child.mu.Unlock()
|
|
|
|
// Remove the entry within the parent.
|
|
parent.RemoveChild(req.Name)
|
|
|
|
// Mark the child as unlinked.
|
|
child.linkCount--
|
|
|
|
return
|
|
}
|
|
|
|
func (fs *memFS) OpenDir(
|
|
ctx context.Context,
|
|
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.).
|
|
inode := fs.getInodeForReadingOrDie(req.Inode)
|
|
defer inode.mu.RUnlock()
|
|
|
|
if !inode.dir {
|
|
panic("Found non-dir.")
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (fs *memFS) ReadDir(
|
|
ctx context.Context,
|
|
req *fuse.ReadDirRequest) (resp *fuse.ReadDirResponse, err error) {
|
|
resp = &fuse.ReadDirResponse{}
|
|
|
|
fs.mu.RLock()
|
|
defer fs.mu.RUnlock()
|
|
|
|
// Grab the directory.
|
|
inode := fs.getInodeForReadingOrDie(req.Inode)
|
|
defer inode.mu.RUnlock()
|
|
|
|
// 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
|
|
}
|
|
|
|
func (fs *memFS) OpenFile(
|
|
ctx context.Context,
|
|
req *fuse.OpenFileRequest) (resp *fuse.OpenFileResponse, err error) {
|
|
resp = &fuse.OpenFileResponse{}
|
|
|
|
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.).
|
|
inode := fs.getInodeForReadingOrDie(req.Inode)
|
|
defer inode.mu.RUnlock()
|
|
|
|
if inode.dir {
|
|
panic("Found directory.")
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (fs *memFS) ReadFile(
|
|
ctx context.Context,
|
|
req *fuse.ReadFileRequest) (resp *fuse.ReadFileResponse, err error) {
|
|
resp = &fuse.ReadFileResponse{}
|
|
|
|
fs.mu.RLock()
|
|
defer fs.mu.RUnlock()
|
|
|
|
// Find the inode in question.
|
|
inode := fs.getInodeForReadingOrDie(req.Inode)
|
|
defer inode.mu.RUnlock()
|
|
|
|
// Serve the request.
|
|
resp.Data = make([]byte, req.Size)
|
|
n, err := inode.ReadAt(resp.Data, req.Offset)
|
|
resp.Data = resp.Data[:n]
|
|
|
|
// Don't return EOF errors; we just indicate EOF to fuse using a short read.
|
|
if err == io.EOF {
|
|
err = nil
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (fs *memFS) WriteFile(
|
|
ctx context.Context,
|
|
req *fuse.WriteFileRequest) (resp *fuse.WriteFileResponse, err error) {
|
|
resp = &fuse.WriteFileResponse{}
|
|
|
|
fs.mu.RLock()
|
|
defer fs.mu.RUnlock()
|
|
|
|
// Find the inode in question.
|
|
inode := fs.getInodeForModifyingOrDie(req.Inode)
|
|
defer inode.mu.Unlock()
|
|
|
|
// Serve the request.
|
|
_, err = inode.WriteAt(req.Data, req.Offset)
|
|
|
|
return
|
|
}
|