serve sftp: fix truncate request being silently ignored

The SFTP serve handler ignored the size attribute of SETSTAT/FSETSTAT
requests, only acting on the modification time. This meant a client
asking to truncate a file (eg setting the final size of an upload, or
an explicit truncate) had no effect at all.

This respects the size attribute (if present) by truncating the file
to the requested size.
This commit is contained in:
Nick Craig-Wood
2026-05-24 14:34:59 +01:00
parent 4dead760dd
commit 90308de5d1
2 changed files with 42 additions and 1 deletions

View File

@@ -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 {

View File

@@ -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 {