From 4f458ca22a4f435b27159175407c592300c3946f Mon Sep 17 00:00:00 2001 From: Rob Herley Date: Sun, 12 Jan 2025 01:44:26 -0500 Subject: [PATCH] support fd_sync for stdio Signed-off-by: Rob Herley --- internal/sys/stdio.go | 13 ++++++++ internal/sys/stdio_test.go | 62 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) diff --git a/internal/sys/stdio.go b/internal/sys/stdio.go index 32c33661eb..7728e80412 100644 --- a/internal/sys/stdio.go +++ b/internal/sys/stdio.go @@ -36,6 +36,19 @@ func (f *writerFile) Write(buf []byte) (int, experimentalsys.Errno) { return n, experimentalsys.UnwrapOSError(err) } +// syncer is a writer that support flushing for fd_sync +type syncer interface { + Sync() error +} + +// Sync implements the same method as documented on sys.File +func (f *writerFile) Sync() experimentalsys.Errno { + if s, ok := f.w.(syncer); ok { + return experimentalsys.UnwrapOSError(s.Sync()) + } + return 0 +} + // noopStdinFile is a fs.ModeDevice file for use implementing FdStdin. This is // safer than reading from os.DevNull as it can never overrun operating system // file descriptors. diff --git a/internal/sys/stdio_test.go b/internal/sys/stdio_test.go index 0f2b321c3b..a08a5720e8 100644 --- a/internal/sys/stdio_test.go +++ b/internal/sys/stdio_test.go @@ -1,6 +1,8 @@ package sys import ( + "bytes" + "io" "io/fs" "os" "testing" @@ -136,3 +138,63 @@ func TestStdio(t *testing.T) { } } } + +type syncable struct { + io.Writer + Syncd bool +} + +func (s *syncable) Sync() error { + s.Syncd = true + return nil +} + +func TestStdioSync(t *testing.T) { + tmpFile, err := os.CreateTemp(t.TempDir(), "anotherfile") + require.NoError(t, err) + defer tmpFile.Close() + + syncbuf := &syncable{Writer: bytes.NewBuffer(nil)} + + stdoutNil, err := stdioWriterFileEntry("stdout", nil) + require.NoError(t, err) + + stdoutFile, err := stdioWriterFileEntry("stdout", tmpFile) + require.NoError(t, err) + + stdoutSync, err := stdioWriterFileEntry("stdout", syncbuf) + require.NoError(t, err) + + tests := []struct { + name string + f *FileEntry + }{ + { + name: "nil", + f: stdoutNil, + }, + { + name: "file", + f: stdoutFile, + }, + { + name: "syncer", + f: stdoutSync, + }, + } + + for _, tt := range tests { + tc := tt + + t.Run(tc.name, func(t *testing.T) { + _, errno := tc.f.File.Write([]byte("hello")) + require.EqualErrno(t, 0, errno) + errno = tc.f.File.Sync() + require.EqualErrno(t, 0, errno) + + if tc.f == stdoutSync { + require.True(t, syncbuf.Syncd) + } + }) + } +}