fusego/samples/memfs/inode.go

345 lines
8.3 KiB
Go
Raw Normal View History

// Copyright 2015 Google Inc. All Rights Reserved.
2015-03-04 00:27:42 +03:00
//
// 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
2015-03-02 06:37:01 +03:00
import (
2015-03-02 08:01:01 +03:00
"fmt"
2015-03-05 20:56:56 +03:00
"io"
2015-03-02 08:01:01 +03:00
"os"
2015-03-02 07:18:23 +03:00
"github.com/jacobsa/fuse"
2015-03-02 07:35:12 +03:00
"github.com/jacobsa/fuse/fuseutil"
"github.com/jacobsa/gcloud/syncutil"
2015-03-02 06:37:01 +03:00
)
// Common attributes for files and directories.
2015-03-02 06:17:52 +03:00
//
// 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
2015-03-02 07:35:12 +03:00
// and reuse an inode ID while it is still in use.
type inode struct {
2015-03-02 07:35:12 +03:00
/////////////////////////
// Constant data
/////////////////////////
// Is this a directory? If not, it is a file.
2015-03-02 07:36:13 +03:00
dir bool
2015-03-02 07:35:12 +03:00
/////////////////////////
// Mutable state
/////////////////////////
mu syncutil.InvariantMutex
2015-03-03 06:56:55 +03:00
// The number of times this inode is linked into a parent directory. This may
// be zero if the inode has been unlinked but not yet forgotten, because some
// process still has an open file handle.
//
// INVARIANT: linkCount >= 0
linkCount int // GUARDED_BY(mu)
2015-03-02 07:35:12 +03:00
// The current attributes of this inode.
//
2015-03-02 07:35:12 +03:00
// 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
2015-03-05 11:35:32 +03:00
// INVARIANT: attributes.Size == len(contents)
2015-03-02 07:35:12 +03:00
attributes fuse.InodeAttributes // GUARDED_BY(mu)
2015-03-02 06:37:01 +03:00
2015-03-03 06:29:49 +03:00
// For directories, entries describing the children of the directory. Unused
// entries are of type DT_Unknown.
2015-03-02 07:35:12 +03:00
//
// 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
2015-03-03 06:29:49 +03:00
// INVARIANT: Contains no duplicate names in used entries.
2015-03-02 07:35:12 +03:00
entries []fuseutil.Dirent // GUARDED_BY(mu)
2015-03-02 07:18:23 +03:00
2015-03-02 07:35:12 +03:00
// For files, the current contents of the file.
//
// INVARIANT: If dir is true, this is nil.
contents []byte // GUARDED_BY(mu)
2015-03-02 07:20:29 +03:00
}
2015-03-02 07:35:12 +03:00
2015-03-03 03:03:03 +03:00
////////////////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////////////////
2015-03-03 06:56:55 +03:00
// Initially the link count is one.
2015-03-03 01:47:36 +03:00
func newInode(attrs fuse.InodeAttributes) (in *inode) {
2015-03-02 07:54:56 +03:00
in = &inode{
2015-03-03 06:56:55 +03:00
linkCount: 1,
2015-03-03 01:47:36 +03:00
dir: (attrs.Mode&os.ModeDir != 0),
attributes: attrs,
2015-03-02 07:54:56 +03:00
}
in.mu = syncutil.NewInvariantMutex(in.checkInvariants)
return
}
2015-03-02 07:35:12 +03:00
2015-03-02 08:01:01 +03:00
func (inode *inode) checkInvariants() {
2015-03-03 06:56:55 +03:00
// Check the link count.
if inode.linkCount < 0 {
panic(fmt.Sprintf("Negative link count: %v", inode.linkCount))
}
2015-03-02 08:01:01 +03:00
// 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) {
2015-03-02 08:03:44 +03:00
panic(
fmt.Sprintf(
"Unexpected mode: %v, dir: %v",
inode.attributes.Mode,
inode.dir))
2015-03-02 08:01:01 +03:00
}
// Check directory-specific stuff.
if inode.dir {
if inode.contents != nil {
panic("Non-nil contents in a directory.")
}
2015-03-02 08:12:54 +03:00
childNames := make(map[string]struct{})
2015-03-02 08:01:01 +03:00
for i, e := range inode.entries {
if e.Offset != fuse.DirOffset(i+1) {
panic(fmt.Sprintf("Unexpected offset: %v", e.Offset))
}
2015-03-02 08:12:54 +03:00
2015-03-03 06:29:49 +03:00
if e.Type != fuseutil.DT_Unknown {
if _, ok := childNames[e.Name]; ok {
panic(fmt.Sprintf("Duplicate name: %s", e.Name))
}
2015-03-02 08:12:54 +03:00
2015-03-03 06:29:49 +03:00
childNames[e.Name] = struct{}{}
}
2015-03-02 08:01:01 +03:00
}
}
// Check file-specific stuff.
if !inode.dir {
if inode.entries != nil {
panic("Non-nil entries in a file.")
}
}
2015-03-05 11:35:32 +03:00
// Check the size.
if inode.attributes.Size != uint64(len(inode.contents)) {
panic(
fmt.Sprintf(
"Unexpected size: %v vs. %v",
inode.attributes.Size,
len(inode.contents)))
}
2015-03-02 08:01:01 +03:00
}
2015-03-02 07:50:34 +03:00
2015-03-03 06:23:46 +03:00
// Return the index of the child within inode.entries, if it exists.
2015-03-02 07:50:34 +03:00
//
// REQUIRES: inode.dir
// SHARED_LOCKS_REQUIRED(inode.mu)
2015-03-03 06:23:46 +03:00
func (inode *inode) findChild(name string) (i int, ok bool) {
2015-03-02 07:54:56 +03:00
if !inode.dir {
2015-03-03 06:23:46 +03:00
panic("findChild called on non-directory.")
2015-03-02 07:54:56 +03:00
}
2015-03-03 06:23:46 +03:00
var e fuseutil.Dirent
for i, e = range inode.entries {
2015-03-02 07:55:26 +03:00
if e.Name == name {
ok = true
return
}
}
return
2015-03-02 07:54:56 +03:00
}
2015-03-03 06:23:46 +03:00
////////////////////////////////////////////////////////////////////////
// Public methods
////////////////////////////////////////////////////////////////////////
2015-03-03 06:39:29 +03:00
// Return the number of children of the directory.
//
// REQUIRES: inode.dir
// SHARED_LOCKS_REQUIRED(inode.mu)
func (inode *inode) Len() (n int) {
for _, e := range inode.entries {
if e.Type != fuseutil.DT_Unknown {
n++
}
}
return
}
2015-03-03 06:23:46 +03:00
// 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) {
index, ok := inode.findChild(name)
if ok {
id = inode.entries[index].Inode
}
return
}
2015-03-03 01:33:33 +03:00
// Add an entry for a child.
//
// REQUIRES: inode.dir
2015-03-03 06:29:49 +03:00
// REQUIRES: dt != fuseutil.DT_Unknown
2015-03-03 01:33:33 +03:00
// EXCLUSIVE_LOCKS_REQUIRED(inode.mu)
func (inode *inode) AddChild(
id fuse.InodeID,
name string,
2015-03-03 01:35:16 +03:00
dt fuseutil.DirentType) {
2015-03-03 06:45:39 +03:00
var index int
// No matter where we place the entry, make sure it has the correct Offset
// field.
defer func() {
inode.entries[index].Offset = fuse.DirOffset(index + 1)
}()
2015-03-03 06:31:15 +03:00
// Set up the entry.
2015-03-03 01:35:16 +03:00
e := fuseutil.Dirent{
2015-03-03 06:45:39 +03:00
Inode: id,
Name: name,
Type: dt,
2015-03-03 01:35:16 +03:00
}
2015-03-03 06:31:15 +03:00
// Look for a gap in which we can insert it.
2015-03-03 06:45:39 +03:00
for index = range inode.entries {
if inode.entries[index].Type == fuseutil.DT_Unknown {
inode.entries[index] = e
2015-03-03 06:31:15 +03:00
return
}
}
2015-03-03 06:29:49 +03:00
2015-03-03 06:31:15 +03:00
// Append it to the end.
2015-03-03 06:45:39 +03:00
index = len(inode.entries)
2015-03-03 06:31:15 +03:00
inode.entries = append(inode.entries, e)
2015-03-03 01:35:16 +03:00
}
2015-03-03 01:33:33 +03:00
2015-03-03 03:28:41 +03:00
// Remove an entry for a child.
//
// REQUIRES: inode.dir
// REQUIRES: An entry for the given name exists.
// EXCLUSIVE_LOCKS_REQUIRED(inode.mu)
2015-03-03 06:29:49 +03:00
func (inode *inode) RemoveChild(name string) {
// Find the entry.
i, ok := inode.findChild(name)
if !ok {
panic(fmt.Sprintf("Unknown child: %s", name))
}
// Mark it as unused.
inode.entries[i] = fuseutil.Dirent{
2015-03-03 06:30:14 +03:00
Type: fuseutil.DT_Unknown,
Offset: fuse.DirOffset(i + 1),
2015-03-03 06:29:49 +03:00
}
}
2015-03-03 03:28:41 +03:00
2015-03-02 07:54:56 +03:00
// Serve a ReadDir request.
//
2015-03-03 06:23:46 +03:00
// REQUIRES: inode.dir
2015-03-02 07:54:56 +03:00
// 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++ {
2015-03-03 06:29:49 +03:00
e := inode.entries[i]
// Skip unused entries.
if e.Type == fuseutil.DT_Unknown {
continue
}
2015-03-02 07:54:56 +03:00
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
}
2015-03-05 11:33:10 +03:00
2015-03-05 20:56:56 +03:00
// Read from the file's contents. See documentation for ioutil.ReaderAt.
//
// REQUIRES: !inode.dir
// SHARED_LOCKS_REQUIRED(inode.mu)
func (inode *inode) ReadAt(p []byte, off int64) (n int, err error) {
if inode.dir {
panic("ReadAt called on directory.")
}
// Ensure the offset is in range.
if off > int64(len(inode.contents)) {
err = io.EOF
return
}
// Read what we can.
n = copy(p, inode.contents[off:])
if n < len(p) {
err = io.EOF
}
return
}
2015-03-05 11:33:10 +03:00
// Write to the file's contents. See documentation for ioutil.WriterAt.
//
// REQUIRES: !inode.dir
// EXCLUSIVE_LOCKS_REQUIRED(inode.mu)
func (inode *inode) WriteAt(p []byte, off int64) (n int, err error) {
if inode.dir {
panic("WriteAt called on directory.")
}
// Ensure that the contents slice is long enough.
newLen := int(off) + len(p)
if len(inode.contents) < newLen {
padding := make([]byte, newLen-len(inode.contents))
inode.contents = append(inode.contents, padding...)
2015-03-05 11:36:09 +03:00
inode.attributes.Size = uint64(newLen)
2015-03-05 11:33:10 +03:00
}
// Copy in the data.
n = copy(inode.contents[off:], p)
// Sanity check.
if n != len(p) {
panic(fmt.Sprintf("Unexpected short copy: %v", n))
}
return
}