Enabled writeback caching by default.
This may bring significant performance improvements, but also comes with some caveats. See the notes on MountConfig.DisableWritebackCaching for details.geesefs-0-30-9
commit
962e8e26d5
|
@ -57,6 +57,7 @@ const maxReadahead = 1 << 20
|
||||||
|
|
||||||
// A connection to the fuse kernel process.
|
// A connection to the fuse kernel process.
|
||||||
type Connection struct {
|
type Connection struct {
|
||||||
|
cfg MountConfig
|
||||||
debugLogger *log.Logger
|
debugLogger *log.Logger
|
||||||
errorLogger *log.Logger
|
errorLogger *log.Logger
|
||||||
|
|
||||||
|
@ -65,9 +66,6 @@ type Connection struct {
|
||||||
dev *os.File
|
dev *os.File
|
||||||
protocol fusekernel.Protocol
|
protocol fusekernel.Protocol
|
||||||
|
|
||||||
// The context from which all op contexts inherit.
|
|
||||||
parentCtx context.Context
|
|
||||||
|
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
|
|
||||||
// A map from fuse "unique" request ID (*not* the op ID for logging used
|
// A map from fuse "unique" request ID (*not* the op ID for logging used
|
||||||
|
@ -94,15 +92,15 @@ type opState struct {
|
||||||
//
|
//
|
||||||
// The loggers may be nil.
|
// The loggers may be nil.
|
||||||
func newConnection(
|
func newConnection(
|
||||||
parentCtx context.Context,
|
cfg MountConfig,
|
||||||
debugLogger *log.Logger,
|
debugLogger *log.Logger,
|
||||||
errorLogger *log.Logger,
|
errorLogger *log.Logger,
|
||||||
dev *os.File) (c *Connection, err error) {
|
dev *os.File) (c *Connection, err error) {
|
||||||
c = &Connection{
|
c = &Connection{
|
||||||
|
cfg: cfg,
|
||||||
debugLogger: debugLogger,
|
debugLogger: debugLogger,
|
||||||
errorLogger: errorLogger,
|
errorLogger: errorLogger,
|
||||||
dev: dev,
|
dev: dev,
|
||||||
parentCtx: parentCtx,
|
|
||||||
cancelFuncs: make(map[uint64]func()),
|
cancelFuncs: make(map[uint64]func()),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -165,6 +163,11 @@ func (c *Connection) Init() (err error) {
|
||||||
// Tell the kernel not to use pitifully small 4 KiB writes.
|
// Tell the kernel not to use pitifully small 4 KiB writes.
|
||||||
initOp.Flags |= fusekernel.InitBigWrites
|
initOp.Flags |= fusekernel.InitBigWrites
|
||||||
|
|
||||||
|
// Enable writeback caching if the user hasn't asked us not to.
|
||||||
|
if !c.cfg.DisableWritebackCaching {
|
||||||
|
initOp.Flags |= fusekernel.InitWritebackCache
|
||||||
|
}
|
||||||
|
|
||||||
c.Reply(ctx, nil)
|
c.Reply(ctx, nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -227,7 +230,7 @@ func (c *Connection) beginOp(
|
||||||
opCode uint32,
|
opCode uint32,
|
||||||
fuseID uint64) (ctx context.Context) {
|
fuseID uint64) (ctx context.Context) {
|
||||||
// Start with the parent context.
|
// Start with the parent context.
|
||||||
ctx = c.parentCtx
|
ctx = c.cfg.OpContext
|
||||||
|
|
||||||
// Set up a cancellation function.
|
// Set up a cancellation function.
|
||||||
//
|
//
|
||||||
|
|
17
debug.go
17
debug.go
|
@ -57,6 +57,23 @@ func describeRequest(op interface{}) (s string) {
|
||||||
addComponent("parent %d", typed.Parent)
|
addComponent("parent %d", typed.Parent)
|
||||||
addComponent("name %q", typed.Name)
|
addComponent("name %q", typed.Name)
|
||||||
|
|
||||||
|
case *fuseops.SetInodeAttributesOp:
|
||||||
|
if typed.Size != nil {
|
||||||
|
addComponent("size %d", *typed.Size)
|
||||||
|
}
|
||||||
|
|
||||||
|
if typed.Mode != nil {
|
||||||
|
addComponent("mode %v", *typed.Mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
if typed.Atime != nil {
|
||||||
|
addComponent("atime %v", *typed.Atime)
|
||||||
|
}
|
||||||
|
|
||||||
|
if typed.Mtime != nil {
|
||||||
|
addComponent("mtime %v", *typed.Mtime)
|
||||||
|
}
|
||||||
|
|
||||||
case *fuseops.ReadFileOp:
|
case *fuseops.ReadFileOp:
|
||||||
addComponent("handle %d", typed.Handle)
|
addComponent("handle %d", typed.Handle)
|
||||||
addComponent("offset %d", typed.Offset)
|
addComponent("offset %d", typed.Offset)
|
||||||
|
|
|
@ -509,17 +509,8 @@ type ReadFileOp struct {
|
||||||
//
|
//
|
||||||
// Note that the kernel *will* ensure that writes are received and acknowledged
|
// Note that the kernel *will* ensure that writes are received and acknowledged
|
||||||
// by the file system before sending a FlushFileOp when closing the file
|
// by the file system before sending a FlushFileOp when closing the file
|
||||||
// descriptor to which they were written:
|
// descriptor to which they were written. Cf. the notes on
|
||||||
//
|
// fuse.MountConfig.DisableWritebackCaching.
|
||||||
// * (http://goo.gl/PheZjf) fuse_flush calls write_inode_now, which appears
|
|
||||||
// to start a writeback in the background (it talks about a "flusher
|
|
||||||
// thread").
|
|
||||||
//
|
|
||||||
// * (http://goo.gl/1IiepM) fuse_flush then calls fuse_sync_writes, which
|
|
||||||
// "[waits] for all pending writepages on the inode to finish".
|
|
||||||
//
|
|
||||||
// * (http://goo.gl/zzvxWv) Only then does fuse_flush finally send the
|
|
||||||
// flush request.
|
|
||||||
//
|
//
|
||||||
// (See also http://goo.gl/ocdTdM, fuse-devel thread "Fuse guarantees on
|
// (See also http://goo.gl/ocdTdM, fuse-devel thread "Fuse guarantees on
|
||||||
// concurrent requests".)
|
// concurrent requests".)
|
||||||
|
|
|
@ -28,28 +28,32 @@ import (
|
||||||
// also that it matches.
|
// also that it matches.
|
||||||
func MtimeIs(expected time.Time) oglematchers.Matcher {
|
func MtimeIs(expected time.Time) oglematchers.Matcher {
|
||||||
return oglematchers.NewMatcher(
|
return oglematchers.NewMatcher(
|
||||||
func(c interface{}) error { return mtimeIs(c, expected) },
|
func(c interface{}) error { return mtimeIsWithin(c, expected, 0) },
|
||||||
fmt.Sprintf("mtime is %v", expected))
|
fmt.Sprintf("mtime is %v", expected))
|
||||||
}
|
}
|
||||||
|
|
||||||
func mtimeIs(c interface{}, expected time.Time) error {
|
// Like MtimeIs, but allows for a tolerance.
|
||||||
|
func MtimeIsWithin(expected time.Time, d time.Duration) oglematchers.Matcher {
|
||||||
|
return oglematchers.NewMatcher(
|
||||||
|
func(c interface{}) error { return mtimeIsWithin(c, expected, d) },
|
||||||
|
fmt.Sprintf("mtime is within %v of %v", d, expected))
|
||||||
|
}
|
||||||
|
|
||||||
|
func mtimeIsWithin(c interface{}, expected time.Time, d time.Duration) error {
|
||||||
fi, ok := c.(os.FileInfo)
|
fi, ok := c.(os.FileInfo)
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("which is of type %v", reflect.TypeOf(c))
|
return fmt.Errorf("which is of type %v", reflect.TypeOf(c))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check ModTime().
|
// Check ModTime().
|
||||||
if fi.ModTime() != expected {
|
diff := fi.ModTime().Sub(expected)
|
||||||
d := fi.ModTime().Sub(expected)
|
absDiff := diff
|
||||||
return fmt.Errorf("which has mtime %v, off by %v", fi.ModTime(), d)
|
if absDiff < 0 {
|
||||||
|
absDiff = -absDiff
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check Sys().
|
if !(absDiff < d) {
|
||||||
if sysMtime, ok := extractMtime(fi.Sys()); ok {
|
return fmt.Errorf("which has mtime %v, off by %v", fi.ModTime(), diff)
|
||||||
if sysMtime != expected {
|
|
||||||
d := sysMtime.Sub(expected)
|
|
||||||
return fmt.Errorf("which has Sys() mtime %v, off by %v", sysMtime, d)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
8
mount.go
8
mount.go
|
@ -67,14 +67,14 @@ func Mount(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Choose a parent context for ops.
|
// Choose a parent context for ops.
|
||||||
opContext := config.OpContext
|
cfgCopy := *config
|
||||||
if opContext == nil {
|
if cfgCopy.OpContext == nil {
|
||||||
opContext = context.Background()
|
cfgCopy.OpContext = context.Background()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a Connection object wrapping the device.
|
// Create a Connection object wrapping the device.
|
||||||
connection, err := newConnection(
|
connection, err := newConnection(
|
||||||
opContext,
|
cfgCopy,
|
||||||
config.DebugLogger,
|
config.DebugLogger,
|
||||||
config.ErrorLogger,
|
config.ErrorLogger,
|
||||||
dev)
|
dev)
|
||||||
|
|
|
@ -48,6 +48,71 @@ type MountConfig struct {
|
||||||
// performed.
|
// performed.
|
||||||
DebugLogger *log.Logger
|
DebugLogger *log.Logger
|
||||||
|
|
||||||
|
// Linux only. OS X always behaves as if writeback caching is disabled.
|
||||||
|
//
|
||||||
|
// By default on Linux we allow the kernel to perform writeback caching
|
||||||
|
// (cf. http://goo.gl/LdZzo1):
|
||||||
|
//
|
||||||
|
// * When the user calls write(2), the kernel sticks the user's data into
|
||||||
|
// its page cache. Only later does it call through to the file system,
|
||||||
|
// potentially after coalescing multiple small user writes.
|
||||||
|
//
|
||||||
|
// * The file system may receive multiple write ops from the kernel
|
||||||
|
// concurrently if there is a lot of page cache data to flush.
|
||||||
|
//
|
||||||
|
// * Write performance may be significantly improved due to the user and
|
||||||
|
// the kernel not waiting for serial round trips to the file system. This
|
||||||
|
// is especially true if the user makes tiny writes.
|
||||||
|
//
|
||||||
|
// * close(2) (and anything else calling f_op->flush) causes all dirty
|
||||||
|
// pages to be written out before it proceeds to send a FlushFileOp
|
||||||
|
// (cf. https://goo.gl/TMrY6X).
|
||||||
|
//
|
||||||
|
// * Similarly, close(2) causes the kernel to send a setattr request
|
||||||
|
// filling in the mtime if any dirty pages were flushed, since the time
|
||||||
|
// at which the pages were written to the file system can't be trusted.
|
||||||
|
//
|
||||||
|
// * close(2) (and anything else calling f_op->flush) writes out all dirty
|
||||||
|
// pages, then sends a setattr request with an appropriate mtime for
|
||||||
|
// those writes if there were any, and only then proceeds to send a flush
|
||||||
|
//
|
||||||
|
// Code walk:
|
||||||
|
//
|
||||||
|
// * (https://goo.gl/zTIZQ9) fuse_flush calls write_inode_now before
|
||||||
|
// calling the file system. The latter eventually calls into
|
||||||
|
// __writeback_single_inode.
|
||||||
|
//
|
||||||
|
// * (https://goo.gl/L7Z2w5) __writeback_single_inode calls
|
||||||
|
// do_writepages, which writes out any dirty pages.
|
||||||
|
//
|
||||||
|
// * (https://goo.gl/DOPgla) __writeback_single_inode later calls
|
||||||
|
// write_inode, which calls into the superblock op struct's write_inode
|
||||||
|
// member. For fuse, this is fuse_write_inode
|
||||||
|
// (cf. https://goo.gl/eDSKOX).
|
||||||
|
//
|
||||||
|
// * (https://goo.gl/PbkGA1) fuse_write_inode calls fuse_flush_times.
|
||||||
|
//
|
||||||
|
// * (https://goo.gl/ig8x9V) fuse_flush_times sends a setttr request
|
||||||
|
// for setting the inode's mtime.
|
||||||
|
//
|
||||||
|
// However, this brings along some caveats:
|
||||||
|
//
|
||||||
|
// * The file system must handle SetInodeAttributesOp or close(2) will fail,
|
||||||
|
// due to the call chain into fuse_flush_times listed above.
|
||||||
|
//
|
||||||
|
// * The kernel caches mtime and ctime regardless of whether the file
|
||||||
|
// system tells it to do so, disregarding the result of further getattr
|
||||||
|
// requests (cf. https://goo.gl/3ZZMUw, https://goo.gl/7WtQUp). It
|
||||||
|
// appears this may be true of the file size, too. Writeback caching may
|
||||||
|
// therefore not be suitable for file systems where these attributes can
|
||||||
|
// spontaneously change for reasons the kernel doesn't observe. See
|
||||||
|
// http://goo.gl/V5WQCN for more discussion.
|
||||||
|
//
|
||||||
|
// Setting DisableWritebackCaching disables this behavior. Instead the file
|
||||||
|
// system is called one or more times for each write(2), and the user's
|
||||||
|
// syscall doesn't return until the file system returns.
|
||||||
|
DisableWritebackCaching bool
|
||||||
|
|
||||||
// OS X only.
|
// OS X only.
|
||||||
//
|
//
|
||||||
// Normally on OS X we mount with the novncache option
|
// Normally on OS X we mount with the novncache option
|
||||||
|
|
|
@ -53,6 +53,10 @@ func (t *cachingFSTest) setUp(
|
||||||
getattrTimeout time.Duration) {
|
getattrTimeout time.Duration) {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
|
// We assert things about whether or not mtimes are cached, but writeback
|
||||||
|
// caching causes them to always be cached. Turn it off.
|
||||||
|
t.MountConfig.DisableWritebackCaching = true
|
||||||
|
|
||||||
// Create the file system.
|
// Create the file system.
|
||||||
t.fs, err = cachingfs.NewCachingFS(lookupEntryTimeout, getattrTimeout)
|
t.fs, err = cachingfs.NewCachingFS(lookupEntryTimeout, getattrTimeout)
|
||||||
AssertEq(nil, err)
|
AssertEq(nil, err)
|
||||||
|
|
|
@ -90,6 +90,29 @@ func (fs *flushFS) barAttributes() fuseops.InodeAttributes {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LOCKS_REQUIRED(fs.mu)
|
||||||
|
func (fs *flushFS) getAttributes(id fuseops.InodeID) (
|
||||||
|
attrs fuseops.InodeAttributes,
|
||||||
|
err error) {
|
||||||
|
switch id {
|
||||||
|
case fuseops.RootInodeID:
|
||||||
|
attrs = fs.rootAttributes()
|
||||||
|
return
|
||||||
|
|
||||||
|
case fooID:
|
||||||
|
attrs = fs.fooAttributes()
|
||||||
|
return
|
||||||
|
|
||||||
|
case barID:
|
||||||
|
attrs = fs.barAttributes()
|
||||||
|
return
|
||||||
|
|
||||||
|
default:
|
||||||
|
err = fuse.ENOENT
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////
|
||||||
// FileSystem methods
|
// FileSystem methods
|
||||||
////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -134,23 +157,20 @@ func (fs *flushFS) GetInodeAttributes(
|
||||||
fs.mu.Lock()
|
fs.mu.Lock()
|
||||||
defer fs.mu.Unlock()
|
defer fs.mu.Unlock()
|
||||||
|
|
||||||
switch op.Inode {
|
op.Attributes, err = fs.getAttributes(op.Inode)
|
||||||
case fuseops.RootInodeID:
|
return
|
||||||
op.Attributes = fs.rootAttributes()
|
}
|
||||||
return
|
|
||||||
|
|
||||||
case fooID:
|
func (fs *flushFS) SetInodeAttributes(
|
||||||
op.Attributes = fs.fooAttributes()
|
ctx context.Context,
|
||||||
return
|
op *fuseops.SetInodeAttributesOp) (err error) {
|
||||||
|
fs.mu.Lock()
|
||||||
|
defer fs.mu.Unlock()
|
||||||
|
|
||||||
case barID:
|
// Ignore any changes and simply return existing attributes.
|
||||||
op.Attributes = fs.barAttributes()
|
op.Attributes, err = fs.getAttributes(op.Inode)
|
||||||
return
|
|
||||||
|
|
||||||
default:
|
return
|
||||||
err = fuse.ENOENT
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fs *flushFS) OpenFile(
|
func (fs *flushFS) OpenFile(
|
||||||
|
|
|
@ -590,6 +590,8 @@ func (t *NoErrorsTest) Mmap_NoMsync_MunmapBeforeClose() {
|
||||||
syscall.MAP_SHARED)
|
syscall.MAP_SHARED)
|
||||||
|
|
||||||
AssertEq(nil, err)
|
AssertEq(nil, err)
|
||||||
|
defer syscall.Munmap(data)
|
||||||
|
|
||||||
AssertEq("taco", string(data))
|
AssertEq("taco", string(data))
|
||||||
|
|
||||||
// Modify the contents.
|
// Modify the contents.
|
||||||
|
@ -638,6 +640,8 @@ func (t *NoErrorsTest) Mmap_NoMsync_CloseBeforeMunmap() {
|
||||||
syscall.MAP_SHARED)
|
syscall.MAP_SHARED)
|
||||||
|
|
||||||
AssertEq(nil, err)
|
AssertEq(nil, err)
|
||||||
|
defer syscall.Munmap(data)
|
||||||
|
|
||||||
AssertEq("taco", string(data))
|
AssertEq("taco", string(data))
|
||||||
|
|
||||||
// Close the file. We should see a flush.
|
// Close the file. We should see a flush.
|
||||||
|
@ -682,6 +686,8 @@ func (t *NoErrorsTest) Mmap_WithMsync_MunmapBeforeClose() {
|
||||||
syscall.MAP_SHARED)
|
syscall.MAP_SHARED)
|
||||||
|
|
||||||
AssertEq(nil, err)
|
AssertEq(nil, err)
|
||||||
|
defer syscall.Munmap(data)
|
||||||
|
|
||||||
AssertEq("taco", string(data))
|
AssertEq("taco", string(data))
|
||||||
|
|
||||||
// Modify the contents.
|
// Modify the contents.
|
||||||
|
@ -738,6 +744,8 @@ func (t *NoErrorsTest) Mmap_WithMsync_CloseBeforeMunmap() {
|
||||||
syscall.MAP_SHARED)
|
syscall.MAP_SHARED)
|
||||||
|
|
||||||
AssertEq(nil, err)
|
AssertEq(nil, err)
|
||||||
|
defer syscall.Munmap(data)
|
||||||
|
|
||||||
AssertEq("taco", string(data))
|
AssertEq("taco", string(data))
|
||||||
|
|
||||||
// Close the file. We should see a flush.
|
// Close the file. We should see a flush.
|
||||||
|
@ -941,6 +949,7 @@ func (t *FsyncErrorTest) Msync() {
|
||||||
syscall.MAP_SHARED)
|
syscall.MAP_SHARED)
|
||||||
|
|
||||||
AssertEq(nil, err)
|
AssertEq(nil, err)
|
||||||
|
defer syscall.Munmap(data)
|
||||||
|
|
||||||
// msync the mapping.
|
// msync the mapping.
|
||||||
err = msync(data)
|
err = msync(data)
|
||||||
|
|
|
@ -22,19 +22,12 @@ import (
|
||||||
|
|
||||||
"github.com/jacobsa/fuse/fuseops"
|
"github.com/jacobsa/fuse/fuseops"
|
||||||
"github.com/jacobsa/fuse/fuseutil"
|
"github.com/jacobsa/fuse/fuseutil"
|
||||||
"github.com/jacobsa/timeutil"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Common attributes for files and directories.
|
// Common attributes for files and directories.
|
||||||
//
|
//
|
||||||
// External synchronization is required.
|
// External synchronization is required.
|
||||||
type inode struct {
|
type inode struct {
|
||||||
/////////////////////////
|
|
||||||
// Dependencies
|
|
||||||
/////////////////////////
|
|
||||||
|
|
||||||
clock timeutil.Clock
|
|
||||||
|
|
||||||
/////////////////////////
|
/////////////////////////
|
||||||
// Mutable state
|
// Mutable state
|
||||||
/////////////////////////
|
/////////////////////////
|
||||||
|
@ -79,16 +72,14 @@ type inode struct {
|
||||||
// Create a new inode with the supplied attributes, which need not contain
|
// Create a new inode with the supplied attributes, which need not contain
|
||||||
// time-related information (the inode object will take care of that).
|
// time-related information (the inode object will take care of that).
|
||||||
func newInode(
|
func newInode(
|
||||||
clock timeutil.Clock,
|
|
||||||
attrs fuseops.InodeAttributes) (in *inode) {
|
attrs fuseops.InodeAttributes) (in *inode) {
|
||||||
// Update time info.
|
// Update time info.
|
||||||
now := clock.Now()
|
now := time.Now()
|
||||||
attrs.Mtime = now
|
attrs.Mtime = now
|
||||||
attrs.Crtime = now
|
attrs.Crtime = now
|
||||||
|
|
||||||
// Create the object.
|
// Create the object.
|
||||||
in = &inode{
|
in = &inode{
|
||||||
clock: clock,
|
|
||||||
attrs: attrs,
|
attrs: attrs,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -226,7 +217,7 @@ func (in *inode) AddChild(
|
||||||
var index int
|
var index int
|
||||||
|
|
||||||
// Update the modification time.
|
// Update the modification time.
|
||||||
in.attrs.Mtime = in.clock.Now()
|
in.attrs.Mtime = time.Now()
|
||||||
|
|
||||||
// No matter where we place the entry, make sure it has the correct Offset
|
// No matter where we place the entry, make sure it has the correct Offset
|
||||||
// field.
|
// field.
|
||||||
|
@ -260,7 +251,7 @@ func (in *inode) AddChild(
|
||||||
// REQUIRES: An entry for the given name exists.
|
// REQUIRES: An entry for the given name exists.
|
||||||
func (in *inode) RemoveChild(name string) {
|
func (in *inode) RemoveChild(name string) {
|
||||||
// Update the modification time.
|
// Update the modification time.
|
||||||
in.attrs.Mtime = in.clock.Now()
|
in.attrs.Mtime = time.Now()
|
||||||
|
|
||||||
// Find the entry.
|
// Find the entry.
|
||||||
i, ok := in.findChild(name)
|
i, ok := in.findChild(name)
|
||||||
|
@ -334,7 +325,7 @@ func (in *inode) WriteAt(p []byte, off int64) (n int, err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the modification time.
|
// Update the modification time.
|
||||||
in.attrs.Mtime = in.clock.Now()
|
in.attrs.Mtime = time.Now()
|
||||||
|
|
||||||
// Ensure that the contents slice is long enough.
|
// Ensure that the contents slice is long enough.
|
||||||
newLen := int(off) + len(p)
|
newLen := int(off) + len(p)
|
||||||
|
@ -361,7 +352,7 @@ func (in *inode) SetAttributes(
|
||||||
mode *os.FileMode,
|
mode *os.FileMode,
|
||||||
mtime *time.Time) {
|
mtime *time.Time) {
|
||||||
// Update the modification time.
|
// Update the modification time.
|
||||||
in.attrs.Mtime = in.clock.Now()
|
in.attrs.Mtime = time.Now()
|
||||||
|
|
||||||
// Truncate?
|
// Truncate?
|
||||||
if size != nil {
|
if size != nil {
|
||||||
|
|
|
@ -26,7 +26,6 @@ import (
|
||||||
"github.com/jacobsa/fuse/fuseops"
|
"github.com/jacobsa/fuse/fuseops"
|
||||||
"github.com/jacobsa/fuse/fuseutil"
|
"github.com/jacobsa/fuse/fuseutil"
|
||||||
"github.com/jacobsa/syncutil"
|
"github.com/jacobsa/syncutil"
|
||||||
"github.com/jacobsa/timeutil"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type memFS struct {
|
type memFS struct {
|
||||||
|
@ -36,12 +35,6 @@ type memFS struct {
|
||||||
uid uint32
|
uid uint32
|
||||||
gid uint32
|
gid uint32
|
||||||
|
|
||||||
/////////////////////////
|
|
||||||
// Dependencies
|
|
||||||
/////////////////////////
|
|
||||||
|
|
||||||
clock timeutil.Clock
|
|
||||||
|
|
||||||
/////////////////////////
|
/////////////////////////
|
||||||
// Mutable state
|
// Mutable state
|
||||||
/////////////////////////
|
/////////////////////////
|
||||||
|
@ -74,11 +67,9 @@ type memFS struct {
|
||||||
// default_permissions option.
|
// default_permissions option.
|
||||||
func NewMemFS(
|
func NewMemFS(
|
||||||
uid uint32,
|
uid uint32,
|
||||||
gid uint32,
|
gid uint32) fuse.Server {
|
||||||
clock timeutil.Clock) fuse.Server {
|
|
||||||
// Set up the basic struct.
|
// Set up the basic struct.
|
||||||
fs := &memFS{
|
fs := &memFS{
|
||||||
clock: clock,
|
|
||||||
inodes: make([]*inode, fuseops.RootInodeID+1),
|
inodes: make([]*inode, fuseops.RootInodeID+1),
|
||||||
uid: uid,
|
uid: uid,
|
||||||
gid: gid,
|
gid: gid,
|
||||||
|
@ -91,7 +82,7 @@ func NewMemFS(
|
||||||
Gid: gid,
|
Gid: gid,
|
||||||
}
|
}
|
||||||
|
|
||||||
fs.inodes[fuseops.RootInodeID] = newInode(clock, rootAttrs)
|
fs.inodes[fuseops.RootInodeID] = newInode(rootAttrs)
|
||||||
|
|
||||||
// Set up invariant checking.
|
// Set up invariant checking.
|
||||||
fs.mu = syncutil.NewInvariantMutex(fs.checkInvariants)
|
fs.mu = syncutil.NewInvariantMutex(fs.checkInvariants)
|
||||||
|
@ -165,7 +156,7 @@ func (fs *memFS) getInodeOrDie(id fuseops.InodeID) (inode *inode) {
|
||||||
func (fs *memFS) allocateInode(
|
func (fs *memFS) allocateInode(
|
||||||
attrs fuseops.InodeAttributes) (id fuseops.InodeID, inode *inode) {
|
attrs fuseops.InodeAttributes) (id fuseops.InodeID, inode *inode) {
|
||||||
// Create the inode.
|
// Create the inode.
|
||||||
inode = newInode(fs.clock, attrs)
|
inode = newInode(attrs)
|
||||||
|
|
||||||
// Re-use a free ID if possible. Otherwise mint a new one.
|
// Re-use a free ID if possible. Otherwise mint a new one.
|
||||||
numFree := len(fs.freeInodes)
|
numFree := len(fs.freeInodes)
|
||||||
|
@ -216,7 +207,7 @@ func (fs *memFS) LookUpInode(
|
||||||
|
|
||||||
// We don't spontaneously mutate, so the kernel can cache as long as it wants
|
// We don't spontaneously mutate, so the kernel can cache as long as it wants
|
||||||
// (since it also handles invalidation).
|
// (since it also handles invalidation).
|
||||||
op.Entry.AttributesExpiration = fs.clock.Now().Add(365 * 24 * time.Hour)
|
op.Entry.AttributesExpiration = time.Now().Add(365 * 24 * time.Hour)
|
||||||
op.Entry.EntryExpiration = op.Entry.EntryExpiration
|
op.Entry.EntryExpiration = op.Entry.EntryExpiration
|
||||||
|
|
||||||
return
|
return
|
||||||
|
@ -236,7 +227,7 @@ func (fs *memFS) GetInodeAttributes(
|
||||||
|
|
||||||
// We don't spontaneously mutate, so the kernel can cache as long as it wants
|
// We don't spontaneously mutate, so the kernel can cache as long as it wants
|
||||||
// (since it also handles invalidation).
|
// (since it also handles invalidation).
|
||||||
op.AttributesExpiration = fs.clock.Now().Add(365 * 24 * time.Hour)
|
op.AttributesExpiration = time.Now().Add(365 * 24 * time.Hour)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -258,7 +249,7 @@ func (fs *memFS) SetInodeAttributes(
|
||||||
|
|
||||||
// We don't spontaneously mutate, so the kernel can cache as long as it wants
|
// We don't spontaneously mutate, so the kernel can cache as long as it wants
|
||||||
// (since it also handles invalidation).
|
// (since it also handles invalidation).
|
||||||
op.AttributesExpiration = fs.clock.Now().Add(365 * 24 * time.Hour)
|
op.AttributesExpiration = time.Now().Add(365 * 24 * time.Hour)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -300,7 +291,7 @@ func (fs *memFS) MkDir(
|
||||||
|
|
||||||
// We don't spontaneously mutate, so the kernel can cache as long as it wants
|
// We don't spontaneously mutate, so the kernel can cache as long as it wants
|
||||||
// (since it also handles invalidation).
|
// (since it also handles invalidation).
|
||||||
op.Entry.AttributesExpiration = fs.clock.Now().Add(365 * 24 * time.Hour)
|
op.Entry.AttributesExpiration = time.Now().Add(365 * 24 * time.Hour)
|
||||||
op.Entry.EntryExpiration = op.Entry.EntryExpiration
|
op.Entry.EntryExpiration = op.Entry.EntryExpiration
|
||||||
|
|
||||||
return
|
return
|
||||||
|
@ -324,7 +315,7 @@ func (fs *memFS) CreateFile(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up attributes from the child.
|
// Set up attributes from the child.
|
||||||
now := fs.clock.Now()
|
now := time.Now()
|
||||||
childAttrs := fuseops.InodeAttributes{
|
childAttrs := fuseops.InodeAttributes{
|
||||||
Nlink: 1,
|
Nlink: 1,
|
||||||
Mode: op.Mode,
|
Mode: op.Mode,
|
||||||
|
@ -348,7 +339,7 @@ func (fs *memFS) CreateFile(
|
||||||
|
|
||||||
// We don't spontaneously mutate, so the kernel can cache as long as it wants
|
// We don't spontaneously mutate, so the kernel can cache as long as it wants
|
||||||
// (since it also handles invalidation).
|
// (since it also handles invalidation).
|
||||||
op.Entry.AttributesExpiration = fs.clock.Now().Add(365 * 24 * time.Hour)
|
op.Entry.AttributesExpiration = time.Now().Add(365 * 24 * time.Hour)
|
||||||
op.Entry.EntryExpiration = op.Entry.EntryExpiration
|
op.Entry.EntryExpiration = op.Entry.EntryExpiration
|
||||||
|
|
||||||
// We have nothing interesting to put in the Handle field.
|
// We have nothing interesting to put in the Handle field.
|
||||||
|
@ -374,7 +365,7 @@ func (fs *memFS) CreateSymlink(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up attributes from the child.
|
// Set up attributes from the child.
|
||||||
now := fs.clock.Now()
|
now := time.Now()
|
||||||
childAttrs := fuseops.InodeAttributes{
|
childAttrs := fuseops.InodeAttributes{
|
||||||
Nlink: 1,
|
Nlink: 1,
|
||||||
Mode: 0444 | os.ModeSymlink,
|
Mode: 0444 | os.ModeSymlink,
|
||||||
|
@ -401,7 +392,7 @@ func (fs *memFS) CreateSymlink(
|
||||||
|
|
||||||
// We don't spontaneously mutate, so the kernel can cache as long as it wants
|
// We don't spontaneously mutate, so the kernel can cache as long as it wants
|
||||||
// (since it also handles invalidation).
|
// (since it also handles invalidation).
|
||||||
op.Entry.AttributesExpiration = fs.clock.Now().Add(365 * 24 * time.Hour)
|
op.Entry.AttributesExpiration = time.Now().Add(365 * 24 * time.Hour)
|
||||||
op.Entry.EntryExpiration = op.Entry.EntryExpiration
|
op.Entry.EntryExpiration = op.Entry.EntryExpiration
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
|
@ -35,6 +35,11 @@ import (
|
||||||
|
|
||||||
func TestMemFS(t *testing.T) { RunTests(t) }
|
func TestMemFS(t *testing.T) { RunTests(t) }
|
||||||
|
|
||||||
|
// The radius we use for "expect mtime is within"-style assertions. We can't
|
||||||
|
// share a synchronized clock with the ultimate source of mtimes because with
|
||||||
|
// writeback caching enabled the kernel manufactures them based on wall time.
|
||||||
|
const timeSlop = 25 * time.Millisecond
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////
|
||||||
// Helpers
|
// Helpers
|
||||||
////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -89,7 +94,7 @@ type MemFSTest struct {
|
||||||
func init() { RegisterTestSuite(&MemFSTest{}) }
|
func init() { RegisterTestSuite(&MemFSTest{}) }
|
||||||
|
|
||||||
func (t *MemFSTest) SetUp(ti *TestInfo) {
|
func (t *MemFSTest) SetUp(ti *TestInfo) {
|
||||||
t.Server = memfs.NewMemFS(currentUid(), currentGid(), &t.Clock)
|
t.Server = memfs.NewMemFS(currentUid(), currentGid())
|
||||||
t.SampleTest.SetUp(ti)
|
t.SampleTest.SetUp(ti)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,17 +117,11 @@ func (t *MemFSTest) Mkdir_OneLevel() {
|
||||||
|
|
||||||
dirName := path.Join(t.Dir, "dir")
|
dirName := path.Join(t.Dir, "dir")
|
||||||
|
|
||||||
// Simulate time advancing.
|
|
||||||
t.Clock.AdvanceTime(time.Second)
|
|
||||||
|
|
||||||
// Create a directory within the root.
|
// Create a directory within the root.
|
||||||
createTime := t.Clock.Now()
|
createTime := time.Now()
|
||||||
err = os.Mkdir(dirName, 0754)
|
err = os.Mkdir(dirName, 0754)
|
||||||
AssertEq(nil, err)
|
AssertEq(nil, err)
|
||||||
|
|
||||||
// Simulate time advancing.
|
|
||||||
t.Clock.AdvanceTime(time.Second)
|
|
||||||
|
|
||||||
// Stat the directory.
|
// Stat the directory.
|
||||||
fi, err = os.Stat(dirName)
|
fi, err = os.Stat(dirName)
|
||||||
stat = fi.Sys().(*syscall.Stat_t)
|
stat = fi.Sys().(*syscall.Stat_t)
|
||||||
|
@ -131,7 +130,7 @@ func (t *MemFSTest) Mkdir_OneLevel() {
|
||||||
ExpectEq("dir", fi.Name())
|
ExpectEq("dir", fi.Name())
|
||||||
ExpectEq(0, fi.Size())
|
ExpectEq(0, fi.Size())
|
||||||
ExpectEq(os.ModeDir|applyUmask(0754), fi.Mode())
|
ExpectEq(os.ModeDir|applyUmask(0754), fi.Mode())
|
||||||
ExpectThat(fi, fusetesting.MtimeIs(createTime))
|
ExpectThat(fi, fusetesting.MtimeIsWithin(createTime, timeSlop))
|
||||||
ExpectThat(fi, fusetesting.BirthtimeIs(createTime))
|
ExpectThat(fi, fusetesting.BirthtimeIs(createTime))
|
||||||
ExpectTrue(fi.IsDir())
|
ExpectTrue(fi.IsDir())
|
||||||
|
|
||||||
|
@ -145,7 +144,7 @@ func (t *MemFSTest) Mkdir_OneLevel() {
|
||||||
fi, err = os.Stat(t.Dir)
|
fi, err = os.Stat(t.Dir)
|
||||||
|
|
||||||
AssertEq(nil, err)
|
AssertEq(nil, err)
|
||||||
ExpectEq(0, fi.ModTime().Sub(createTime))
|
ExpectThat(fi, fusetesting.MtimeIsWithin(createTime, timeSlop))
|
||||||
|
|
||||||
// Read the directory.
|
// Read the directory.
|
||||||
entries, err = fusetesting.ReadDirPicky(dirName)
|
entries, err = fusetesting.ReadDirPicky(dirName)
|
||||||
|
@ -174,17 +173,11 @@ func (t *MemFSTest) Mkdir_TwoLevels() {
|
||||||
err = os.Mkdir(path.Join(t.Dir, "parent"), 0700)
|
err = os.Mkdir(path.Join(t.Dir, "parent"), 0700)
|
||||||
AssertEq(nil, err)
|
AssertEq(nil, err)
|
||||||
|
|
||||||
// Simulate time advancing.
|
|
||||||
t.Clock.AdvanceTime(time.Second)
|
|
||||||
|
|
||||||
// Create a child of that directory.
|
// Create a child of that directory.
|
||||||
createTime := t.Clock.Now()
|
createTime := time.Now()
|
||||||
err = os.Mkdir(path.Join(t.Dir, "parent/dir"), 0754)
|
err = os.Mkdir(path.Join(t.Dir, "parent/dir"), 0754)
|
||||||
AssertEq(nil, err)
|
AssertEq(nil, err)
|
||||||
|
|
||||||
// Simulate time advancing.
|
|
||||||
t.Clock.AdvanceTime(time.Second)
|
|
||||||
|
|
||||||
// Stat the directory.
|
// Stat the directory.
|
||||||
fi, err = os.Stat(path.Join(t.Dir, "parent/dir"))
|
fi, err = os.Stat(path.Join(t.Dir, "parent/dir"))
|
||||||
stat = fi.Sys().(*syscall.Stat_t)
|
stat = fi.Sys().(*syscall.Stat_t)
|
||||||
|
@ -193,7 +186,7 @@ func (t *MemFSTest) Mkdir_TwoLevels() {
|
||||||
ExpectEq("dir", fi.Name())
|
ExpectEq("dir", fi.Name())
|
||||||
ExpectEq(0, fi.Size())
|
ExpectEq(0, fi.Size())
|
||||||
ExpectEq(os.ModeDir|applyUmask(0754), fi.Mode())
|
ExpectEq(os.ModeDir|applyUmask(0754), fi.Mode())
|
||||||
ExpectThat(fi, fusetesting.MtimeIs(createTime))
|
ExpectThat(fi, fusetesting.MtimeIsWithin(createTime, timeSlop))
|
||||||
ExpectThat(fi, fusetesting.BirthtimeIs(createTime))
|
ExpectThat(fi, fusetesting.BirthtimeIs(createTime))
|
||||||
ExpectTrue(fi.IsDir())
|
ExpectTrue(fi.IsDir())
|
||||||
|
|
||||||
|
@ -206,7 +199,7 @@ func (t *MemFSTest) Mkdir_TwoLevels() {
|
||||||
// Check the parent's mtime.
|
// Check the parent's mtime.
|
||||||
fi, err = os.Stat(path.Join(t.Dir, "parent"))
|
fi, err = os.Stat(path.Join(t.Dir, "parent"))
|
||||||
AssertEq(nil, err)
|
AssertEq(nil, err)
|
||||||
ExpectEq(0, fi.ModTime().Sub(createTime))
|
ExpectThat(fi, fusetesting.MtimeIsWithin(createTime, timeSlop))
|
||||||
|
|
||||||
// Read the directory.
|
// Read the directory.
|
||||||
entries, err = fusetesting.ReadDirPicky(path.Join(t.Dir, "parent/dir"))
|
entries, err = fusetesting.ReadDirPicky(path.Join(t.Dir, "parent/dir"))
|
||||||
|
@ -290,13 +283,10 @@ func (t *MemFSTest) CreateNewFile_InRoot() {
|
||||||
fileName := path.Join(t.Dir, "foo")
|
fileName := path.Join(t.Dir, "foo")
|
||||||
const contents = "Hello\x00world"
|
const contents = "Hello\x00world"
|
||||||
|
|
||||||
createTime := t.Clock.Now()
|
createTime := time.Now()
|
||||||
err = ioutil.WriteFile(fileName, []byte(contents), 0400)
|
err = ioutil.WriteFile(fileName, []byte(contents), 0400)
|
||||||
AssertEq(nil, err)
|
AssertEq(nil, err)
|
||||||
|
|
||||||
// Simulate time advancing.
|
|
||||||
t.Clock.AdvanceTime(time.Second)
|
|
||||||
|
|
||||||
// Stat it.
|
// Stat it.
|
||||||
fi, err = os.Stat(fileName)
|
fi, err = os.Stat(fileName)
|
||||||
stat = fi.Sys().(*syscall.Stat_t)
|
stat = fi.Sys().(*syscall.Stat_t)
|
||||||
|
@ -305,7 +295,7 @@ func (t *MemFSTest) CreateNewFile_InRoot() {
|
||||||
ExpectEq("foo", fi.Name())
|
ExpectEq("foo", fi.Name())
|
||||||
ExpectEq(len(contents), fi.Size())
|
ExpectEq(len(contents), fi.Size())
|
||||||
ExpectEq(applyUmask(0400), fi.Mode())
|
ExpectEq(applyUmask(0400), fi.Mode())
|
||||||
ExpectThat(fi, fusetesting.MtimeIs(createTime))
|
ExpectThat(fi, fusetesting.MtimeIsWithin(createTime, timeSlop))
|
||||||
ExpectThat(fi, fusetesting.BirthtimeIs(createTime))
|
ExpectThat(fi, fusetesting.BirthtimeIs(createTime))
|
||||||
ExpectFalse(fi.IsDir())
|
ExpectFalse(fi.IsDir())
|
||||||
|
|
||||||
|
@ -335,13 +325,10 @@ func (t *MemFSTest) CreateNewFile_InSubDir() {
|
||||||
fileName := path.Join(dirName, "foo")
|
fileName := path.Join(dirName, "foo")
|
||||||
const contents = "Hello\x00world"
|
const contents = "Hello\x00world"
|
||||||
|
|
||||||
createTime := t.Clock.Now()
|
createTime := time.Now()
|
||||||
err = ioutil.WriteFile(fileName, []byte(contents), 0400)
|
err = ioutil.WriteFile(fileName, []byte(contents), 0400)
|
||||||
AssertEq(nil, err)
|
AssertEq(nil, err)
|
||||||
|
|
||||||
// Simulate time advancing.
|
|
||||||
t.Clock.AdvanceTime(time.Second)
|
|
||||||
|
|
||||||
// Stat it.
|
// Stat it.
|
||||||
fi, err = os.Stat(fileName)
|
fi, err = os.Stat(fileName)
|
||||||
stat = fi.Sys().(*syscall.Stat_t)
|
stat = fi.Sys().(*syscall.Stat_t)
|
||||||
|
@ -350,7 +337,7 @@ func (t *MemFSTest) CreateNewFile_InSubDir() {
|
||||||
ExpectEq("foo", fi.Name())
|
ExpectEq("foo", fi.Name())
|
||||||
ExpectEq(len(contents), fi.Size())
|
ExpectEq(len(contents), fi.Size())
|
||||||
ExpectEq(applyUmask(0400), fi.Mode())
|
ExpectEq(applyUmask(0400), fi.Mode())
|
||||||
ExpectThat(fi, fusetesting.MtimeIs(createTime))
|
ExpectThat(fi, fusetesting.MtimeIsWithin(createTime, timeSlop))
|
||||||
ExpectThat(fi, fusetesting.BirthtimeIs(createTime))
|
ExpectThat(fi, fusetesting.BirthtimeIs(createTime))
|
||||||
ExpectFalse(fi.IsDir())
|
ExpectFalse(fi.IsDir())
|
||||||
|
|
||||||
|
@ -375,26 +362,20 @@ func (t *MemFSTest) ModifyExistingFile_InRoot() {
|
||||||
// Write a file.
|
// Write a file.
|
||||||
fileName := path.Join(t.Dir, "foo")
|
fileName := path.Join(t.Dir, "foo")
|
||||||
|
|
||||||
createTime := t.Clock.Now()
|
createTime := time.Now()
|
||||||
err = ioutil.WriteFile(fileName, []byte("Hello, world!"), 0600)
|
err = ioutil.WriteFile(fileName, []byte("Hello, world!"), 0600)
|
||||||
AssertEq(nil, err)
|
AssertEq(nil, err)
|
||||||
|
|
||||||
// Simulate time advancing.
|
|
||||||
t.Clock.AdvanceTime(time.Second)
|
|
||||||
|
|
||||||
// Open the file and modify it.
|
// Open the file and modify it.
|
||||||
f, err := os.OpenFile(fileName, os.O_WRONLY, 0400)
|
f, err := os.OpenFile(fileName, os.O_WRONLY, 0400)
|
||||||
t.ToClose = append(t.ToClose, f)
|
t.ToClose = append(t.ToClose, f)
|
||||||
AssertEq(nil, err)
|
AssertEq(nil, err)
|
||||||
|
|
||||||
modifyTime := t.Clock.Now()
|
modifyTime := time.Now()
|
||||||
n, err = f.WriteAt([]byte("H"), 0)
|
n, err = f.WriteAt([]byte("H"), 0)
|
||||||
AssertEq(nil, err)
|
AssertEq(nil, err)
|
||||||
AssertEq(1, n)
|
AssertEq(1, n)
|
||||||
|
|
||||||
// Simulate time advancing.
|
|
||||||
t.Clock.AdvanceTime(time.Second)
|
|
||||||
|
|
||||||
// Stat the file.
|
// Stat the file.
|
||||||
fi, err = os.Stat(fileName)
|
fi, err = os.Stat(fileName)
|
||||||
stat = fi.Sys().(*syscall.Stat_t)
|
stat = fi.Sys().(*syscall.Stat_t)
|
||||||
|
@ -403,7 +384,7 @@ func (t *MemFSTest) ModifyExistingFile_InRoot() {
|
||||||
ExpectEq("foo", fi.Name())
|
ExpectEq("foo", fi.Name())
|
||||||
ExpectEq(len("Hello, world!"), fi.Size())
|
ExpectEq(len("Hello, world!"), fi.Size())
|
||||||
ExpectEq(applyUmask(0600), fi.Mode())
|
ExpectEq(applyUmask(0600), fi.Mode())
|
||||||
ExpectThat(fi, fusetesting.MtimeIs(modifyTime))
|
ExpectThat(fi, fusetesting.MtimeIsWithin(modifyTime, timeSlop))
|
||||||
ExpectThat(fi, fusetesting.BirthtimeIs(createTime))
|
ExpectThat(fi, fusetesting.BirthtimeIs(createTime))
|
||||||
ExpectFalse(fi.IsDir())
|
ExpectFalse(fi.IsDir())
|
||||||
|
|
||||||
|
@ -433,26 +414,20 @@ func (t *MemFSTest) ModifyExistingFile_InSubDir() {
|
||||||
// Write a file.
|
// Write a file.
|
||||||
fileName := path.Join(dirName, "foo")
|
fileName := path.Join(dirName, "foo")
|
||||||
|
|
||||||
createTime := t.Clock.Now()
|
createTime := time.Now()
|
||||||
err = ioutil.WriteFile(fileName, []byte("Hello, world!"), 0600)
|
err = ioutil.WriteFile(fileName, []byte("Hello, world!"), 0600)
|
||||||
AssertEq(nil, err)
|
AssertEq(nil, err)
|
||||||
|
|
||||||
// Simulate time advancing.
|
|
||||||
t.Clock.AdvanceTime(time.Second)
|
|
||||||
|
|
||||||
// Open the file and modify it.
|
// Open the file and modify it.
|
||||||
f, err := os.OpenFile(fileName, os.O_WRONLY, 0400)
|
f, err := os.OpenFile(fileName, os.O_WRONLY, 0400)
|
||||||
t.ToClose = append(t.ToClose, f)
|
t.ToClose = append(t.ToClose, f)
|
||||||
AssertEq(nil, err)
|
AssertEq(nil, err)
|
||||||
|
|
||||||
modifyTime := t.Clock.Now()
|
modifyTime := time.Now()
|
||||||
n, err = f.WriteAt([]byte("H"), 0)
|
n, err = f.WriteAt([]byte("H"), 0)
|
||||||
AssertEq(nil, err)
|
AssertEq(nil, err)
|
||||||
AssertEq(1, n)
|
AssertEq(1, n)
|
||||||
|
|
||||||
// Simulate time advancing.
|
|
||||||
t.Clock.AdvanceTime(time.Second)
|
|
||||||
|
|
||||||
// Stat the file.
|
// Stat the file.
|
||||||
fi, err = os.Stat(fileName)
|
fi, err = os.Stat(fileName)
|
||||||
stat = fi.Sys().(*syscall.Stat_t)
|
stat = fi.Sys().(*syscall.Stat_t)
|
||||||
|
@ -461,7 +436,7 @@ func (t *MemFSTest) ModifyExistingFile_InSubDir() {
|
||||||
ExpectEq("foo", fi.Name())
|
ExpectEq("foo", fi.Name())
|
||||||
ExpectEq(len("Hello, world!"), fi.Size())
|
ExpectEq(len("Hello, world!"), fi.Size())
|
||||||
ExpectEq(applyUmask(0600), fi.Mode())
|
ExpectEq(applyUmask(0600), fi.Mode())
|
||||||
ExpectThat(fi, fusetesting.MtimeIs(modifyTime))
|
ExpectThat(fi, fusetesting.MtimeIsWithin(modifyTime, timeSlop))
|
||||||
ExpectThat(fi, fusetesting.BirthtimeIs(createTime))
|
ExpectThat(fi, fusetesting.BirthtimeIs(createTime))
|
||||||
ExpectFalse(fi.IsDir())
|
ExpectFalse(fi.IsDir())
|
||||||
|
|
||||||
|
@ -574,17 +549,11 @@ func (t *MemFSTest) Rmdir_Empty() {
|
||||||
err = os.MkdirAll(path.Join(t.Dir, "foo/bar"), 0754)
|
err = os.MkdirAll(path.Join(t.Dir, "foo/bar"), 0754)
|
||||||
AssertEq(nil, err)
|
AssertEq(nil, err)
|
||||||
|
|
||||||
// Simulate time advancing.
|
|
||||||
t.Clock.AdvanceTime(time.Second)
|
|
||||||
|
|
||||||
// Remove the leaf.
|
// Remove the leaf.
|
||||||
rmTime := t.Clock.Now()
|
rmTime := time.Now()
|
||||||
err = os.Remove(path.Join(t.Dir, "foo/bar"))
|
err = os.Remove(path.Join(t.Dir, "foo/bar"))
|
||||||
AssertEq(nil, err)
|
AssertEq(nil, err)
|
||||||
|
|
||||||
// Simulate time advancing.
|
|
||||||
t.Clock.AdvanceTime(time.Second)
|
|
||||||
|
|
||||||
// There should be nothing left in the parent.
|
// There should be nothing left in the parent.
|
||||||
entries, err = fusetesting.ReadDirPicky(path.Join(t.Dir, "foo"))
|
entries, err = fusetesting.ReadDirPicky(path.Join(t.Dir, "foo"))
|
||||||
|
|
||||||
|
@ -594,7 +563,7 @@ func (t *MemFSTest) Rmdir_Empty() {
|
||||||
// Check the parent's mtime.
|
// Check the parent's mtime.
|
||||||
fi, err := os.Stat(path.Join(t.Dir, "foo"))
|
fi, err := os.Stat(path.Join(t.Dir, "foo"))
|
||||||
AssertEq(nil, err)
|
AssertEq(nil, err)
|
||||||
ExpectEq(0, fi.ModTime().Sub(rmTime))
|
ExpectThat(fi, fusetesting.MtimeIsWithin(rmTime, timeSlop))
|
||||||
|
|
||||||
// Remove the parent.
|
// Remove the parent.
|
||||||
err = os.Remove(path.Join(t.Dir, "foo"))
|
err = os.Remove(path.Join(t.Dir, "foo"))
|
||||||
|
@ -618,13 +587,10 @@ func (t *MemFSTest) Rmdir_OpenedForReading() {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
// Create a directory.
|
// Create a directory.
|
||||||
createTime := t.Clock.Now()
|
createTime := time.Now()
|
||||||
err = os.Mkdir(path.Join(t.Dir, "dir"), 0700)
|
err = os.Mkdir(path.Join(t.Dir, "dir"), 0700)
|
||||||
AssertEq(nil, err)
|
AssertEq(nil, err)
|
||||||
|
|
||||||
// Simulate time advancing.
|
|
||||||
t.Clock.AdvanceTime(time.Second)
|
|
||||||
|
|
||||||
// Open the directory for reading.
|
// Open the directory for reading.
|
||||||
f, err := os.Open(path.Join(t.Dir, "dir"))
|
f, err := os.Open(path.Join(t.Dir, "dir"))
|
||||||
defer func() {
|
defer func() {
|
||||||
|
@ -655,7 +621,7 @@ func (t *MemFSTest) Rmdir_OpenedForReading() {
|
||||||
fi, err := f.Stat()
|
fi, err := f.Stat()
|
||||||
|
|
||||||
ExpectEq("dir", fi.Name())
|
ExpectEq("dir", fi.Name())
|
||||||
ExpectEq(0, fi.ModTime().Sub(createTime))
|
ExpectThat(fi, fusetesting.MtimeIsWithin(createTime, timeSlop))
|
||||||
ExpectEq(0, fi.Sys().(*syscall.Stat_t).Nlink)
|
ExpectEq(0, fi.Sys().(*syscall.Stat_t).Nlink)
|
||||||
|
|
||||||
// Attempt to read from the directory. This shouldn't see any junk from the
|
// Attempt to read from the directory. This shouldn't see any junk from the
|
||||||
|
@ -1054,7 +1020,7 @@ func (t *MemFSTest) Chtimes() {
|
||||||
// Stat it.
|
// Stat it.
|
||||||
fi, err := os.Stat(fileName)
|
fi, err := os.Stat(fileName)
|
||||||
AssertEq(nil, err)
|
AssertEq(nil, err)
|
||||||
ExpectEq(0, fi.ModTime().Sub(expectedMtime))
|
ExpectThat(fi, fusetesting.MtimeIsWithin(expectedMtime, timeSlop))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *MemFSTest) ReadDirWhileModifying() {
|
func (t *MemFSTest) ReadDirWhileModifying() {
|
||||||
|
|
Loading…
Reference in New Issue