mount: replace use of WriteAt with Write for cache mode >= writes and O_APPEND

os.File.WriteAt returns an error if a file was opened with O_APPEND.
This replaces it with os.File.Write if the file was opened with
O_APPEND.
This commit is contained in:
Brett Dutro 2019-10-14 19:57:15 -05:00 committed by Nick Craig-Wood
parent daff5a824e
commit 378a3f4133
6 changed files with 72 additions and 3 deletions

View File

@ -371,7 +371,12 @@ func (fsys *FS) Write(path string, buff []byte, ofst int64, fh uint64) (n int) {
if errc != 0 { if errc != 0 {
return errc return errc
} }
n, err := handle.WriteAt(buff, ofst) var err error
if fsys.VFS.Opt.CacheMode < vfs.CacheModeWrites || handle.Node().Mode()&os.ModeAppend == 0 {
n, err = handle.WriteAt(buff, ofst)
} else {
n, err = handle.Write(buff)
}
if err != nil { if err != nil {
return translateError(err) return translateError(err)
} }

View File

@ -5,6 +5,7 @@ package mount
import ( import (
"context" "context"
"io" "io"
"os"
"bazil.org/fuse" "bazil.org/fuse"
fusefs "bazil.org/fuse/fs" fusefs "bazil.org/fuse/fs"
@ -41,7 +42,12 @@ var _ fusefs.HandleWriter = (*FileHandle)(nil)
// Write data to the file handle // Write data to the file handle
func (fh *FileHandle) Write(ctx context.Context, req *fuse.WriteRequest, resp *fuse.WriteResponse) (err error) { func (fh *FileHandle) Write(ctx context.Context, req *fuse.WriteRequest, resp *fuse.WriteResponse) (err error) {
defer log.Trace(fh, "len=%d, offset=%d", len(req.Data), req.Offset)("written=%d, err=%v", &resp.Size, &err) defer log.Trace(fh, "len=%d, offset=%d", len(req.Data), req.Offset)("written=%d, err=%v", &resp.Size, &err)
n, err := fh.Handle.WriteAt(req.Data, req.Offset) var n int
if fh.Handle.Node().VFS().Opt.CacheMode < vfs.CacheModeWrites || fh.Handle.Node().Mode()&os.ModeAppend == 0 {
n, err = fh.Handle.WriteAt(req.Data, req.Offset)
} else {
n, err = fh.Handle.Write(req.Data)
}
if err != nil { if err != nil {
return translateError(err) return translateError(err)
} }

View File

@ -34,6 +34,11 @@ func osCreate(name string) (*os.File, error) {
return os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666) return os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
} }
// os.Create with append
func osAppend(name string) (*os.File, error) {
return os.OpenFile(name, os.O_WRONLY|os.O_APPEND, 0666)
}
// TestFileModTimeWithOpenWriters tests mod time on open files // TestFileModTimeWithOpenWriters tests mod time on open files
func TestFileModTimeWithOpenWriters(t *testing.T) { func TestFileModTimeWithOpenWriters(t *testing.T) {
run.skipIfNoFUSE(t) run.skipIfNoFUSE(t)

View File

@ -78,6 +78,7 @@ func RunTests(t *testing.T, fn MountFn) {
t.Run("TestWriteFileDoubleClose", TestWriteFileDoubleClose) t.Run("TestWriteFileDoubleClose", TestWriteFileDoubleClose)
t.Run("TestWriteFileFsync", TestWriteFileFsync) t.Run("TestWriteFileFsync", TestWriteFileFsync)
t.Run("TestWriteFileDup", TestWriteFileDup) t.Run("TestWriteFileDup", TestWriteFileDup)
t.Run("TestWriteFileAppend", TestWriteFileAppend)
}) })
log.Printf("Finished test run with cache mode %v (ok=%v)", cacheMode, ok) log.Printf("Finished test run with cache mode %v (ok=%v)", cacheMode, ok)
if !ok { if !ok {

View File

@ -2,6 +2,7 @@ package mounttest
import ( import (
"os" "os"
"runtime"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -130,3 +131,48 @@ func TestWriteFileDup(t *testing.T) {
run.waitForWriters() run.waitForWriters()
run.rm(t, "to be synced") run.rm(t, "to be synced")
} }
// TestWriteFileAppend tests that O_APPEND works on cache backends >= writes
func TestWriteFileAppend(t *testing.T) {
run.skipIfNoFUSE(t)
if run.vfs.Opt.CacheMode < vfs.CacheModeWrites {
t.Skip("not supported on vfs-cache-mode < writes")
return
}
// TODO: Windows needs the v1.5 release of WinFsp to handle O_APPEND properly.
// Until it gets released, skip this test on Windows.
if runtime.GOOS == "windows" {
t.Skip("currently unsupported on Windows")
}
filepath := run.path("to be synced")
fh, err := osCreate(filepath)
require.NoError(t, err)
testData := []byte("0123456789")
appendData := []byte("10")
_, err = fh.Write(testData)
require.NoError(t, err)
err = fh.Close()
require.NoError(t, err)
fh, err = osAppend(filepath)
require.NoError(t, err)
_, err = fh.Write(appendData)
require.NoError(t, err)
err = fh.Close()
require.NoError(t, err)
info, err := os.Stat(filepath)
require.NoError(t, err)
require.EqualValues(t, len(testData)+len(appendData), info.Size())
run.waitForWriters()
run.rm(t, "to be synced")
}

View File

@ -31,6 +31,7 @@ type File struct {
modified bool // has the cache file be modified by a RWFileHandle? modified bool // has the cache file be modified by a RWFileHandle?
pendingModTime time.Time // will be applied once o becomes available, i.e. after file was written pendingModTime time.Time // will be applied once o becomes available, i.e. after file was written
pendingRenameFun func(ctx context.Context) error // will be run/renamed after all writers close pendingRenameFun func(ctx context.Context) error // will be run/renamed after all writers close
appendMode bool // file was opened with O_APPEND
muRW sync.Mutex // synchonize RWFileHandle.openPending(), RWFileHandle.close() and File.Remove muRW sync.Mutex // synchonize RWFileHandle.openPending(), RWFileHandle.close() and File.Remove
} }
@ -65,7 +66,11 @@ func (f *File) IsDir() bool {
// Mode bits of the file or directory - satisfies Node interface // Mode bits of the file or directory - satisfies Node interface
func (f *File) Mode() (mode os.FileMode) { func (f *File) Mode() (mode os.FileMode) {
return f.d.vfs.Opt.FilePerms mode = f.d.vfs.Opt.FilePerms
if f.appendMode {
mode |= os.ModeAppend
}
return mode
} }
// Name (base) of the directory - satisfies Node interface // Name (base) of the directory - satisfies Node interface
@ -539,6 +544,7 @@ func (f *File) Open(flags int) (fd Handle, err error) {
// If append is set then set read to force openRW // If append is set then set read to force openRW
if flags&os.O_APPEND != 0 { if flags&os.O_APPEND != 0 {
read = true read = true
f.appendMode = true
} }
// If truncate is set then set write to force openRW // If truncate is set then set write to force openRW