diff --git a/cmd/cmount/fs.go b/cmd/cmount/fs.go index d0e5665a8..90c640b46 100644 --- a/cmd/cmount/fs.go +++ b/cmd/cmount/fs.go @@ -371,7 +371,12 @@ func (fsys *FS) Write(path string, buff []byte, ofst int64, fh uint64) (n int) { if errc != 0 { 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 { return translateError(err) } diff --git a/cmd/mount/handle.go b/cmd/mount/handle.go index 28bcb921f..f2d5d76bd 100644 --- a/cmd/mount/handle.go +++ b/cmd/mount/handle.go @@ -5,6 +5,7 @@ package mount import ( "context" "io" + "os" "bazil.org/fuse" fusefs "bazil.org/fuse/fs" @@ -41,7 +42,12 @@ var _ fusefs.HandleWriter = (*FileHandle)(nil) // Write data to the file handle 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) - 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 { return translateError(err) } diff --git a/cmd/mountlib/mounttest/file.go b/cmd/mountlib/mounttest/file.go index b5768fa88..9fd55ce75 100644 --- a/cmd/mountlib/mounttest/file.go +++ b/cmd/mountlib/mounttest/file.go @@ -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) } +// 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 func TestFileModTimeWithOpenWriters(t *testing.T) { run.skipIfNoFUSE(t) diff --git a/cmd/mountlib/mounttest/fs.go b/cmd/mountlib/mounttest/fs.go index e8bb8415b..b919042c8 100644 --- a/cmd/mountlib/mounttest/fs.go +++ b/cmd/mountlib/mounttest/fs.go @@ -78,6 +78,7 @@ func RunTests(t *testing.T, fn MountFn) { t.Run("TestWriteFileDoubleClose", TestWriteFileDoubleClose) t.Run("TestWriteFileFsync", TestWriteFileFsync) t.Run("TestWriteFileDup", TestWriteFileDup) + t.Run("TestWriteFileAppend", TestWriteFileAppend) }) log.Printf("Finished test run with cache mode %v (ok=%v)", cacheMode, ok) if !ok { diff --git a/cmd/mountlib/mounttest/write.go b/cmd/mountlib/mounttest/write.go index 29812a54a..2173eb90b 100644 --- a/cmd/mountlib/mounttest/write.go +++ b/cmd/mountlib/mounttest/write.go @@ -2,6 +2,7 @@ package mounttest import ( "os" + "runtime" "testing" "github.com/stretchr/testify/assert" @@ -130,3 +131,48 @@ func TestWriteFileDup(t *testing.T) { run.waitForWriters() 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") +} diff --git a/vfs/file.go b/vfs/file.go index a28480235..ed6b19ca2 100644 --- a/vfs/file.go +++ b/vfs/file.go @@ -31,6 +31,7 @@ type File struct { 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 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 } @@ -65,7 +66,11 @@ func (f *File) IsDir() bool { // Mode bits of the file or directory - satisfies Node interface 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 @@ -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 flags&os.O_APPEND != 0 { read = true + f.appendMode = true } // If truncate is set then set write to force openRW