Fixed mtime tracking, tightened up other tests.

geesefs-0-30-9
Aaron Jacobs 2015-03-06 05:36:34 +11:00
commit 720e853031
4 changed files with 428 additions and 22 deletions

View File

@ -74,7 +74,7 @@ func NewMemFS(
Mode: 0700 | os.ModeDir,
}
fs.inodes[fuse.RootInodeID] = newInode(rootAttrs)
fs.inodes[fuse.RootInodeID] = newInode(clock, rootAttrs)
// Set up invariant checking.
fs.mu = syncutil.NewInvariantMutex(fs.checkInvariants)
@ -163,7 +163,7 @@ func (fs *memFS) getInodeForReadingOrDie(id fuse.InodeID) (inode *inode) {
func (fs *memFS) allocateInode(
attrs fuse.InodeAttributes) (id fuse.InodeID, inode *inode) {
// Create and lock the inode.
inode = newInode(attrs)
inode = newInode(fs.clock, attrs)
inode.mu.Lock()
// Re-use a free ID if possible. Otherwise mint a new one.
@ -281,15 +281,10 @@ func (fs *memFS) MkDir(
// 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,
Mode: req.Mode,
Uid: req.Header.Uid,
Gid: req.Header.Gid,
}
// Allocate a child.

View File

@ -22,6 +22,7 @@ import (
"github.com/jacobsa/fuse"
"github.com/jacobsa/fuse/fuseutil"
"github.com/jacobsa/gcloud/syncutil"
"github.com/jacobsa/gcsfuse/timeutil"
)
// Common attributes for files and directories.
@ -30,6 +31,12 @@ import (
// been unlinked, including creating a new file. Make sure we don't screw up
// and reuse an inode ID while it is still in use.
type inode struct {
/////////////////////////
// Dependencies
/////////////////////////
clock timeutil.Clock
/////////////////////////
// Constant data
/////////////////////////
@ -84,9 +91,20 @@ type inode struct {
// Helpers
////////////////////////////////////////////////////////////////////////
// Create a new inode with the supplied attributes, which need not contain
// time-related information (the inode object will take care of that).
// Initially the link count is one.
func newInode(attrs fuse.InodeAttributes) (in *inode) {
func newInode(
clock timeutil.Clock,
attrs fuse.InodeAttributes) (in *inode) {
// Update time info.
now := clock.Now()
attrs.Mtime = now
attrs.Crtime = now
// Create the object.
in = &inode{
clock: clock,
linkCount: 1,
dir: (attrs.Mode&os.ModeDir != 0),
attributes: attrs,
@ -217,6 +235,9 @@ func (inode *inode) AddChild(
dt fuseutil.DirentType) {
var index int
// Update the modification time.
inode.attributes.Mtime = inode.clock.Now()
// No matter where we place the entry, make sure it has the correct Offset
// field.
defer func() {
@ -249,6 +270,9 @@ func (inode *inode) AddChild(
// REQUIRES: An entry for the given name exists.
// EXCLUSIVE_LOCKS_REQUIRED(inode.mu)
func (inode *inode) RemoveChild(name string) {
// Update the modification time.
inode.attributes.Mtime = inode.clock.Now()
// Find the entry.
i, ok := inode.findChild(name)
if !ok {
@ -324,6 +348,9 @@ func (inode *inode) WriteAt(p []byte, off int64) (n int, err error) {
panic("WriteAt called on directory.")
}
// Update the modification time.
inode.attributes.Mtime = inode.clock.Now()
// Ensure that the contents slice is long enough.
newLen := int(off) + len(p)
if len(inode.contents) < newLen {

View File

@ -15,6 +15,7 @@
package memfs_test
import (
"io"
"io/ioutil"
"log"
"os"
@ -79,6 +80,9 @@ func timespecToTime(ts syscall.Timespec) time.Time {
type MemFSTest struct {
clock timeutil.SimulatedClock
mfs *fuse.MountedFileSystem
// Files to close when tearing down. Nil entries are skipped.
toClose []io.Closer
}
var _ SetUpInterface = &MemFSTest{}
@ -110,6 +114,18 @@ func (t *MemFSTest) SetUp(ti *TestInfo) {
}
func (t *MemFSTest) TearDown() {
// Close any files we opened.
for _, c := range t.toClose {
if c == nil {
continue
}
err := c.Close()
if err != nil {
panic(err)
}
}
// Unmount the file system. Try again on "resource busy" errors.
delay := 10 * time.Millisecond
for {
@ -152,6 +168,9 @@ func (t *MemFSTest) Mkdir_OneLevel() {
dirName := path.Join(t.mfs.Dir(), "dir")
// Simulate time advancing.
t.clock.AdvanceTime(time.Second)
// Create a directory within the root.
createTime := t.clock.Now()
err = os.Mkdir(dirName, 0754)
@ -176,9 +195,14 @@ func (t *MemFSTest) Mkdir_OneLevel() {
ExpectEq(currentUid(), stat.Uid)
ExpectEq(currentGid(), stat.Gid)
ExpectEq(0, stat.Size)
ExpectEq(0, timespecToTime(stat.Atimespec).Sub(createTime))
ExpectEq(0, timespecToTime(stat.Mtimespec).Sub(createTime))
ExpectEq(0, timespecToTime(stat.Ctimespec).Sub(createTime))
ExpectEq(0, timespecToTime(stat.Birthtimespec).Sub(createTime))
// Check the root's mtime.
fi, err = os.Stat(t.mfs.Dir())
AssertEq(nil, err)
ExpectEq(0, fi.ModTime().Sub(createTime))
// Read the directory.
entries, err = ioutil.ReadDir(dirName)
@ -207,6 +231,9 @@ func (t *MemFSTest) Mkdir_TwoLevels() {
err = os.Mkdir(path.Join(t.mfs.Dir(), "parent"), 0700)
AssertEq(nil, err)
// Simulate time advancing.
t.clock.AdvanceTime(time.Second)
// Create a child of that directory.
createTime := t.clock.Now()
err = os.Mkdir(path.Join(t.mfs.Dir(), "parent/dir"), 0754)
@ -231,9 +258,13 @@ func (t *MemFSTest) Mkdir_TwoLevels() {
ExpectEq(currentUid(), stat.Uid)
ExpectEq(currentGid(), stat.Gid)
ExpectEq(0, stat.Size)
ExpectEq(0, timespecToTime(stat.Atimespec).Sub(createTime))
ExpectEq(0, timespecToTime(stat.Mtimespec).Sub(createTime))
ExpectEq(0, timespecToTime(stat.Ctimespec).Sub(createTime))
ExpectEq(0, timespecToTime(stat.Birthtimespec).Sub(createTime))
// Check the parent's mtime.
fi, err = os.Stat(path.Join(t.mfs.Dir(), "parent"))
AssertEq(nil, err)
ExpectEq(0, fi.ModTime().Sub(createTime))
// Read the directory.
entries, err = ioutil.ReadDir(path.Join(t.mfs.Dir(), "parent/dir"))
@ -313,10 +344,10 @@ func (t *MemFSTest) CreateNewFile_InRoot() {
var fi os.FileInfo
var stat *syscall.Stat_t
// Write a file.
fileName := path.Join(t.mfs.Dir(), "foo")
const contents = "Hello\x00world"
// Write a file.
createTime := t.clock.Now()
err = ioutil.WriteFile(fileName, []byte(contents), 0400)
AssertEq(nil, err)
@ -340,9 +371,8 @@ func (t *MemFSTest) CreateNewFile_InRoot() {
ExpectEq(currentUid(), stat.Uid)
ExpectEq(currentGid(), stat.Gid)
ExpectEq(len(contents), stat.Size)
ExpectEq(0, timespecToTime(stat.Atimespec).Sub(createTime))
ExpectEq(0, timespecToTime(stat.Mtimespec).Sub(createTime))
ExpectEq(0, timespecToTime(stat.Ctimespec).Sub(createTime))
ExpectEq(0, timespecToTime(stat.Birthtimespec).Sub(createTime))
// Read it back.
slice, err := ioutil.ReadFile(fileName)
@ -351,11 +381,103 @@ func (t *MemFSTest) CreateNewFile_InRoot() {
}
func (t *MemFSTest) CreateNewFile_InSubDir() {
AssertTrue(false, "TODO")
var err error
var fi os.FileInfo
var stat *syscall.Stat_t
// Create a sub-dir.
dirName := path.Join(t.mfs.Dir(), "dir")
err = os.Mkdir(dirName, 0700)
AssertEq(nil, err)
// Write a file.
fileName := path.Join(dirName, "foo")
const contents = "Hello\x00world"
createTime := t.clock.Now()
err = ioutil.WriteFile(fileName, []byte(contents), 0400)
AssertEq(nil, err)
// Simulate time advancing.
t.clock.AdvanceTime(time.Second)
// Stat it.
fi, err = os.Stat(fileName)
stat = fi.Sys().(*syscall.Stat_t)
AssertEq(nil, err)
ExpectEq("foo", fi.Name())
ExpectEq(len(contents), fi.Size())
ExpectEq(0400, fi.Mode())
ExpectEq(0, fi.ModTime().Sub(createTime))
ExpectFalse(fi.IsDir())
ExpectNe(0, stat.Ino)
ExpectEq(1, stat.Nlink)
ExpectEq(currentUid(), stat.Uid)
ExpectEq(currentGid(), stat.Gid)
ExpectEq(len(contents), stat.Size)
ExpectEq(0, timespecToTime(stat.Mtimespec).Sub(createTime))
ExpectEq(0, timespecToTime(stat.Birthtimespec).Sub(createTime))
// Read it back.
slice, err := ioutil.ReadFile(fileName)
AssertEq(nil, err)
ExpectEq(contents, string(slice))
}
func (t *MemFSTest) ModifyExistingFile_InRoot() {
AssertTrue(false, "TODO")
var err error
var n int
var fi os.FileInfo
var stat *syscall.Stat_t
// Write a file.
fileName := path.Join(t.mfs.Dir(), "foo")
createTime := t.clock.Now()
err = ioutil.WriteFile(fileName, []byte("Jello, world!"), 0600)
AssertEq(nil, err)
// Simulate time advancing.
t.clock.AdvanceTime(time.Second)
// Open the file and modify it.
f, err := os.OpenFile(fileName, os.O_WRONLY, 0400)
t.toClose = append(t.toClose, f)
AssertEq(nil, err)
modifyTime := t.clock.Now()
n, err = f.WriteAt([]byte("H"), 0)
AssertEq(nil, err)
AssertEq(1, n)
// Simulate time advancing.
t.clock.AdvanceTime(time.Second)
// Stat the file.
fi, err = os.Stat(fileName)
stat = fi.Sys().(*syscall.Stat_t)
AssertEq(nil, err)
ExpectEq("foo", fi.Name())
ExpectEq(len("Hello, world!"), fi.Size())
ExpectEq(0600, fi.Mode())
ExpectEq(0, fi.ModTime().Sub(modifyTime))
ExpectFalse(fi.IsDir())
ExpectNe(0, stat.Ino)
ExpectEq(1, stat.Nlink)
ExpectEq(currentUid(), stat.Uid)
ExpectEq(currentGid(), stat.Gid)
ExpectEq(len("Hello, world!"), stat.Size)
ExpectEq(0, timespecToTime(stat.Mtimespec).Sub(modifyTime))
ExpectEq(0, timespecToTime(stat.Birthtimespec).Sub(createTime))
// Read the file back.
slice, err := ioutil.ReadFile(fileName)
AssertEq(nil, err)
ExpectEq("Hello, world!", string(slice))
}
func (t *MemFSTest) ModifyExistingFile_InSubDir() {
@ -400,16 +522,28 @@ func (t *MemFSTest) Rmdir_Empty() {
err = os.MkdirAll(path.Join(t.mfs.Dir(), "foo/bar"), 0754)
AssertEq(nil, err)
// Simulate time advancing.
t.clock.AdvanceTime(time.Second)
// Remove the leaf.
rmTime := t.clock.Now()
err = os.Remove(path.Join(t.mfs.Dir(), "foo/bar"))
AssertEq(nil, err)
// Simulate time advancing.
t.clock.AdvanceTime(time.Second)
// There should be nothing left in the parent.
entries, err = ioutil.ReadDir(path.Join(t.mfs.Dir(), "foo"))
AssertEq(nil, err)
ExpectThat(entries, ElementsAre())
// Check the parent's mtime.
fi, err := os.Stat(path.Join(t.mfs.Dir(), "foo"))
AssertEq(nil, err)
ExpectEq(0, fi.ModTime().Sub(rmTime))
// Remove the parent.
err = os.Remove(path.Join(t.mfs.Dir(), "foo"))
AssertEq(nil, err)
@ -515,6 +649,210 @@ func (t *MemFSTest) FileReadsAndWrites() {
AssertTrue(false, "TODO")
}
func (t *MemFSTest) FileReadsAndWrites_BeyondEOF() {
AssertTrue(false, "TODO")
func (t *MemFSTest) WriteOverlapsEndOfFile() {
var err error
var n int
// Create a file.
f, err := os.Create(path.Join(t.mfs.Dir(), "foo"))
t.toClose = append(t.toClose, f)
AssertEq(nil, err)
// Make it 4 bytes long.
err = f.Truncate(4)
AssertEq(nil, err)
// Write the range [2, 6).
n, err = f.WriteAt([]byte("taco"), 2)
AssertEq(nil, err)
AssertEq(4, n)
// Read the full contents of the file.
contents, err := ioutil.ReadAll(f)
AssertEq(nil, err)
ExpectEq("\x00\x00taco", string(contents))
}
func (t *MemFSTest) WriteStartsAtEndOfFile() {
var err error
var n int
// Create a file.
f, err := os.Create(path.Join(t.mfs.Dir(), "foo"))
t.toClose = append(t.toClose, f)
AssertEq(nil, err)
// Make it 2 bytes long.
err = f.Truncate(2)
AssertEq(nil, err)
// Write the range [2, 6).
n, err = f.WriteAt([]byte("taco"), 2)
AssertEq(nil, err)
AssertEq(4, n)
// Read the full contents of the file.
contents, err := ioutil.ReadAll(f)
AssertEq(nil, err)
ExpectEq("\x00\x00taco", string(contents))
}
func (t *MemFSTest) WriteStartsPastEndOfFile() {
var err error
var n int
// Create a file.
f, err := os.Create(path.Join(t.mfs.Dir(), "foo"))
t.toClose = append(t.toClose, f)
AssertEq(nil, err)
// Write the range [2, 6).
n, err = f.WriteAt([]byte("taco"), 2)
AssertEq(nil, err)
AssertEq(4, n)
// Read the full contents of the file.
contents, err := ioutil.ReadAll(f)
AssertEq(nil, err)
ExpectEq("\x00\x00taco", string(contents))
}
func (t *MemFSTest) WriteAtDoesntChangeOffset_NotAppendMode() {
var err error
var n int
// Create a file.
f, err := os.Create(path.Join(t.mfs.Dir(), "foo"))
t.toClose = append(t.toClose, f)
AssertEq(nil, err)
// Make it 16 bytes long.
err = f.Truncate(16)
AssertEq(nil, err)
// Seek to offset 4.
_, err = f.Seek(4, 0)
AssertEq(nil, err)
// Write the range [10, 14).
n, err = f.WriteAt([]byte("taco"), 2)
AssertEq(nil, err)
AssertEq(4, n)
// We should still be at offset 4.
offset, err := getFileOffset(f)
AssertEq(nil, err)
ExpectEq(4, offset)
}
func (t *MemFSTest) WriteAtDoesntChangeOffset_AppendMode() {
var err error
var n int
// Create a file in append mode.
f, err := os.OpenFile(
path.Join(t.mfs.Dir(), "foo"),
os.O_RDWR|os.O_APPEND|os.O_CREATE,
0600)
t.toClose = append(t.toClose, f)
AssertEq(nil, err)
// Make it 16 bytes long.
err = f.Truncate(16)
AssertEq(nil, err)
// Seek to offset 4.
_, err = f.Seek(4, 0)
AssertEq(nil, err)
// Write the range [10, 14).
n, err = f.WriteAt([]byte("taco"), 2)
AssertEq(nil, err)
AssertEq(4, n)
// We should still be at offset 4.
offset, err := getFileOffset(f)
AssertEq(nil, err)
ExpectEq(4, offset)
}
func (t *MemFSTest) AppendMode() {
var err error
var n int
var off int64
buf := make([]byte, 1024)
// Create a file with some contents.
fileName := path.Join(t.mfs.Dir(), "foo")
err = ioutil.WriteFile(fileName, []byte("Jello, "), 0600)
AssertEq(nil, err)
// Open the file in append mode.
f, err := os.OpenFile(fileName, os.O_RDWR|os.O_APPEND, 0600)
t.toClose = append(t.toClose, f)
AssertEq(nil, err)
// Seek to somewhere silly and then write.
off, err = f.Seek(2, 0)
AssertEq(nil, err)
AssertEq(2, off)
n, err = f.Write([]byte("world!"))
AssertEq(nil, err)
AssertEq(6, n)
// The offset should have been updated to point at the end of the file.
off, err = getFileOffset(f)
AssertEq(nil, err)
ExpectEq(13, off)
// A random write should still work, without updating the offset.
n, err = f.WriteAt([]byte("H"), 0)
AssertEq(nil, err)
AssertEq(1, n)
off, err = getFileOffset(f)
AssertEq(nil, err)
ExpectEq(13, off)
// Read back the contents of the file, which should be correct even though we
// seeked to a silly place before writing the world part.
n, err = f.ReadAt(buf, 0)
AssertEq(io.EOF, err)
ExpectEq("Hello, world!", string(buf[:n]))
}
func (t *MemFSTest) ReadsPastEndOfFile() {
var err error
var n int
buf := make([]byte, 1024)
// Create a file.
f, err := os.Create(path.Join(t.mfs.Dir(), "foo"))
t.toClose = append(t.toClose, f)
AssertEq(nil, err)
// Give it some contents.
n, err = f.Write([]byte("taco"))
AssertEq(nil, err)
AssertEq(4, n)
// Read a range overlapping EOF.
n, err = f.ReadAt(buf[:4], 2)
AssertEq(io.EOF, err)
ExpectEq(2, n)
ExpectEq("co", string(buf[:n]))
// Read a range starting at EOF.
n, err = f.ReadAt(buf[:4], 4)
AssertEq(io.EOF, err)
ExpectEq(0, n)
ExpectEq("", string(buf[:n]))
// Read a range starting past EOF.
n, err = f.ReadAt(buf[:4], 100)
AssertEq(io.EOF, err)
ExpectEq(0, n)
ExpectEq("", string(buf[:n]))
}

View File

@ -218,6 +218,52 @@ func (t *PosixTest) WriteAtDoesntChangeOffset_AppendMode() {
ExpectEq(4, offset)
}
func (t *PosixTest) AppendMode() {
var err error
var n int
var off int64
buf := make([]byte, 1024)
// Create a file with some contents.
fileName := path.Join(t.dir, "foo")
err = ioutil.WriteFile(fileName, []byte("Jello, "), 0600)
AssertEq(nil, err)
// Open the file in append mode.
f, err := os.OpenFile(fileName, os.O_RDWR|os.O_APPEND, 0600)
t.toClose = append(t.toClose, f)
AssertEq(nil, err)
// Seek to somewhere silly and then write.
off, err = f.Seek(2, 0)
AssertEq(nil, err)
AssertEq(2, off)
n, err = f.Write([]byte("world!"))
AssertEq(nil, err)
AssertEq(6, n)
// The offset should have been updated to point at the end of the file.
off, err = getFileOffset(f)
AssertEq(nil, err)
ExpectEq(13, off)
// A random write should still work, without updating the offset.
n, err = f.WriteAt([]byte("H"), 0)
AssertEq(nil, err)
AssertEq(1, n)
off, err = getFileOffset(f)
AssertEq(nil, err)
ExpectEq(13, off)
// Read back the contents of the file, which should be correct even though we
// seeked to a silly place before writing the world part.
n, err = f.ReadAt(buf, 0)
AssertEq(io.EOF, err)
ExpectEq("Hello, world!", string(buf[:n]))
}
func (t *PosixTest) ReadsPastEndOfFile() {
var err error
var n int