diff --git a/cmd/serve/sftp/handler.go b/cmd/serve/sftp/handler.go index 985b5998a..559580064 100644 --- a/cmd/serve/sftp/handler.go +++ b/cmd/serve/sftp/handler.go @@ -64,7 +64,18 @@ func (v vfsHandler) Filecmd(r *sftp.Request) error { switch r.Method { case "Setstat": attr := r.Attributes() - if attr.Mtime != 0 { + flags := r.AttrFlags() + // A size attribute is a request to truncate the file + if flags.Size { + node, err := v.Stat(r.Filepath) + if err != nil { + return err + } + if err := node.Truncate(int64(attr.Size)); err != nil { + return err + } + } + if flags.Acmodtime { modTime := time.Unix(int64(attr.Mtime), 0) err := v.Chtimes(r.Filepath, modTime, modTime) if err != nil { diff --git a/cmd/serve/sftp/handler_test.go b/cmd/serve/sftp/handler_test.go index 1b91c8dc0..b41d09cc9 100644 --- a/cmd/serve/sftp/handler_test.go +++ b/cmd/serve/sftp/handler_test.go @@ -138,6 +138,36 @@ func TestFilewriteTruncate(t *testing.T) { assert.Equal(t, newContents, string(got)) } +// Test that a SETSTAT request with a size attribute truncates the file, rather +// than being silently ignored. +func TestSetstatTruncate(t *testing.T) { + vfsOpt := vfscommon.Opt + vfsOpt.CacheMode = vfscommon.CacheModeWrites + client := startTestServer(t, &vfsOpt) + + const fileName = "file.bin" + + // Write a file and then truncate it via FSETSTAT (a size attribute) on the + // open handle, the way a client setting the final size of an upload does. + f, err := client.OpenFile(fileName, os.O_WRONLY|os.O_CREATE|os.O_TRUNC) + require.NoError(t, err) + _, err = f.Write([]byte(strings.Repeat("A", 1024))) + require.NoError(t, err) + require.NoError(t, f.Truncate(10)) + require.NoError(t, f.Close()) + + fi, err := client.Stat(fileName) + require.NoError(t, err) + assert.Equal(t, int64(10), fi.Size(), "file not truncated to requested size") + + rd, err := client.Open(fileName) + require.NoError(t, err) + got, err := io.ReadAll(rd) + require.NoError(t, err) + require.NoError(t, rd.Close()) + assert.Equal(t, strings.Repeat("A", 10), string(got)) +} + // writeFile writes contents to fileName via the client truncating any existing // data, the way a normal upload does. func writeFile(client *sftp.Client, fileName, contents string) error {