mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-10-14 18:18:35 +02:00
sftp: keep a file's existing bytes when it is opened for write without truncate (#11723)
* sftp: keep a file's existing bytes when it is opened for write without truncate * sftp: preserve sparse gaps and refuse unreadable entries when copying existing content The copy used a single chunk-stream read, which ends at the first sparse gap: the tail past a hole was dropped, and a remote-only entry with no local chunks copied zero bytes, so closing the handle uploaded an empty file. Read through PrepareStreamContent, which fills unstored regions with zeros, and refuse entries that carry no local bytes at all. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * filer: report a failed destination write from a completed chunk fetch retriedStreamFetchChunkData broke out of the retry loop on a clean HTTP end before checking writeErr, so a destination write that failed mid- stream returned a short write and nil error. Check writeErr first. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * sftp: cover remote and all-holes sizes when copying existing content Take the declared size from RemoteEntry when it exceeds FileSize, pad inline content to the declared size, and treat a chunkless local file as all holes rather than unreadable. A remote-only entry still refuses the copy, now checked with a zero FileSize as well. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * sftp: size the existing-content copy the way filer.FileSize does A locally shortened file still names its older, larger remote object; taking RemoteSize unconditionally padded the copy back out to it. filer.FileSize only honors the remote size when the remote is newer, and also covers the chunk-extent size. A remote-only entry still refuses the copy whenever it points at remote bytes. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: Chris Lu <chrislusf@users.noreply.github.com> Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
5 files changed
+339
-3
No files matched your search
@@ -3,6 +3,7 @@ package sftp
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
@@ -602,6 +603,32 @@ func TestFileContent(t *testing.T) {
|
||||
sftpClient.Remove("/binary.bin")
|
||||
})
|
||||
|
||||
t.Run("WriteAtOffsetKeepsExistingBytes", func(t *testing.T) {
|
||||
file, err := sftpClient.Create("/resume.txt")
|
||||
require.NoError(t, err)
|
||||
_, err = file.Write([]byte("hello world"))
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, file.Close())
|
||||
|
||||
// Reopen without O_TRUNC and write past the start, as a resumed upload does
|
||||
file, err = sftpClient.OpenFile("/resume.txt", os.O_WRONLY)
|
||||
require.NoError(t, err)
|
||||
_, err = file.WriteAt([]byte("WORLD!"), 6)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, file.Close())
|
||||
|
||||
readFile, err := sftpClient.Open("/resume.txt")
|
||||
require.NoError(t, err)
|
||||
content, err := io.ReadAll(readFile)
|
||||
require.NoError(t, err)
|
||||
readFile.Close()
|
||||
|
||||
require.Equal(t, "hello WORLD!", string(content))
|
||||
|
||||
// Clean up
|
||||
sftpClient.Remove("/resume.txt")
|
||||
})
|
||||
|
||||
t.Run("EmptyFile", func(t *testing.T) {
|
||||
file, err := sftpClient.Create("/empty.txt")
|
||||
require.NoError(t, err)
|
||||
|
||||
Reference in new issue
Block a user