Files
seaweedfs/test/sftp
6840371774 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>
2026-10-13 07:20:20 +08:00
..
fmt
2025-12-19 15:33:16 -08:00

SeaweedFS SFTP Integration Tests

This directory contains integration tests for the SeaweedFS SFTP server.

Prerequisites

  1. Build the SeaweedFS binary:

    cd ../../weed
    go build -o weed .
    
  2. Ensure ssh-keygen is available (for generating test SSH host keys)

Running Tests

Run all tests

make test

Run tests with verbose output

make test-verbose

Run a specific test

go test -v -run TestHomeDirPathTranslation

Skip long-running tests

go test -short ./...

Test Structure

  • framework.go - Test framework that starts SeaweedFS cluster with SFTP
  • basic_test.go - Basic SFTP operation tests including:
    • HomeDir path translation (fixes issue #7470)
    • File upload/download
    • Directory operations
    • Large file handling
    • Edge cases

Test Configuration

Tests use testdata/userstore.json which defines test users:

Username Password HomeDir Permissions
admin adminpassword / Full access
testuser testuserpassword /sftp/testuser Full access to home
readonly readonlypassword /public Read-only

Key Tests

TestHomeDirPathTranslation

Tests the fix for issue #7470 where users with a non-root HomeDir (e.g., /sftp/testuser) could not upload files to / because the path wasn't being translated to their home directory.

The test verifies:

  • Uploading to / correctly maps to the user's HomeDir
  • Creating directories at / works
  • Listing / shows the user's home directory contents
  • All path operations respect the HomeDir translation

Debugging

To debug test failures:

  1. Enable verbose output:

    go test -v -run TestName
    
  2. Keep test artifacts (don't cleanup):

    config := DefaultTestConfig()
    config.SkipCleanup = true
    
  3. Enable debug logging:

    config := DefaultTestConfig()
    config.EnableDebug = true