Files
seaweedfs/weed/replication/sink/localsink/local_sink_windows_test.go
T
Chris Lu 7b07d8177a fix(filer.sync): scope filesystem key sanitization to the local sink (#9894)
* fix(filer.sync): scope filesystem key sanitization to the local sink

destKey ran every sink key through escapeKey, whose Windows build strips
colons. Colons are illegal in NTFS filenames so the local sink needs that,
but s3/filer/azure/gcs/b2 accept them as ordinary key bytes — stripping
them silently diverged the destination key (a source a:b replicated as ab).

Move the sanitization into the local sink behind a Windows build tag,
applied at every entry point so the previously-unescaped in-place-update
paths stay consistent. Non-local sinks now keep the raw key; non-Windows
builds are unchanged; a leading drive-letter colon is preserved.

* test(filer.sync): cover incremental destKey and localsink update/delete sanitization

Lock the colon-preserving behavior for the incremental destKey branch, and
extend the Windows local-sink test to assert UpdateEntry and DeleteEntry also
sanitize the key, not just CreateEntry.
2026-06-09 10:18:49 -07:00

66 lines
2.0 KiB
Go

//go:build windows
package localsink
import (
"os"
"path/filepath"
"testing"
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
"github.com/seaweedfs/seaweedfs/weed/replication/source"
)
func TestSanitizeFsKeyStripsColons(t *testing.T) {
if got := sanitizeFsKey("/backup/a:b/c:d.txt"); got != "/backup/ab/cd.txt" {
t.Errorf("sanitizeFsKey() = %q, want %q", got, "/backup/ab/cd.txt")
}
if got := sanitizeFsKey(`C:\a:b`); got != `C:\ab` {
t.Errorf("sanitizeFsKey() = %q, want drive letter preserved %q", got, `C:\ab`)
}
}
// TestCreateEntryWritesSanitizedPath confirms CreateEntry strips colons from
// the destination path before writing, so keys land at NTFS-legal paths.
func TestCreateEntryWritesSanitizedPath(t *testing.T) {
tmpDir := t.TempDir()
sink := &LocalSink{}
sink.initialize(tmpDir, false)
sink.SetSourceFiler(&source.FilerSource{})
key := filepath.Join(tmpDir, "20:24", "fi:le.txt")
entry := &filer_pb.Entry{
Attributes: &filer_pb.FuseAttributes{FileMode: uint32(0644)},
Content: []byte("data"),
}
if err := sink.CreateEntry(key, entry, nil); err != nil {
t.Fatalf("CreateEntry failed: %v", err)
}
want := sanitizeFsKey(key)
if _, err := os.Stat(want); err != nil {
t.Errorf("expected file at sanitized path %q: %v", want, err)
}
if _, err := os.Stat(key); err == nil {
t.Errorf("unexpected file at unsanitized path %q", key)
}
updated := &filer_pb.Entry{
Attributes: &filer_pb.FuseAttributes{FileMode: uint32(0644)},
Content: []byte("data-v2"),
}
if _, err := sink.UpdateEntry(key, entry, filepath.Dir(key), updated, false, nil); err != nil {
t.Fatalf("UpdateEntry failed: %v", err)
}
if _, err := os.Stat(want); err != nil {
t.Errorf("expected updated file at sanitized path %q: %v", want, err)
}
if err := sink.DeleteEntry(key, false, false, nil); err != nil {
t.Fatalf("DeleteEntry failed: %v", err)
}
if _, err := os.Stat(want); !os.IsNotExist(err) {
t.Errorf("expected sanitized path %q removed, stat err=%v", want, err)
}
}