Don't mangle filer paths with the OS separator on Windows (#9878)

fix: don't mangle filer paths with the OS separator on Windows

filepath.Dir/Join use the platform separator, so on Windows they rewrite
a forward-slash filer path like /buckets/x into \buckets\x. The mangled
value then goes into a filer RPC and operates on the wrong key, so the
op silently targets nothing.

The admin file browser hit this in New Folder (the entry landed under
\buckets\my-bucket and never showed up under /buckets/my-bucket), and
the same way in delete, view and properties. MQ topic retention and
consumer-offset listing, and the SFTP home dir plus create-permission
parent lookup, had the same bug.

Switch all of these to the path package, which always uses "/".
This commit is contained in:
Chris Lu
2026-06-08 13:56:02 -07:00
committed by GitHub
parent 8cc10460b4
commit 4c050ad76b
5 changed files with 26 additions and 27 deletions
+3 -3
View File
@@ -4,7 +4,7 @@ import (
"context"
"fmt"
"io"
"path/filepath"
"path"
"strings"
"time"
@@ -325,7 +325,7 @@ func (s *AdminServer) GetConsumerGroupOffsets(namespace, topicName string) ([]Co
// Only process directories that are versions (start with "v")
if versionResp.Entry != nil && versionResp.Entry.IsDirectory && strings.HasPrefix(versionResp.Entry.Name, "v") {
versionDir := filepath.Join(topicDir, versionResp.Entry.Name)
versionDir := path.Join(topicDir, versionResp.Entry.Name)
// List all partition directories under the version directory (e.g., 0315-0630)
partitionStream, err := client.ListEntries(context.Background(), &filer_pb.ListEntriesRequest{
@@ -360,7 +360,7 @@ func (s *AdminServer) GetConsumerGroupOffsets(namespace, topicName string) ([]Co
continue
}
partitionDir := filepath.Join(versionDir, partitionResp.Entry.Name)
partitionDir := path.Join(versionDir, partitionResp.Entry.Name)
// List all .offset files in this partition directory
offsetStream, err := client.ListEntries(context.Background(), &filer_pb.ListEntriesRequest{
+5 -5
View File
@@ -4,7 +4,7 @@ import (
"context"
"fmt"
"io"
"path/filepath"
"path"
"sort"
"strings"
"time"
@@ -187,7 +187,7 @@ func (p *TopicRetentionPurger) purgeTopicData(topicRetention TopicRetentionConfi
// Check if this version directory is expired
if versionDir.VersionTime.Before(cutoffTime) {
dirPath := filepath.Join(topicDir, versionDir.Name)
dirPath := path.Join(topicDir, versionDir.Name)
// Delete the entire version directory
err := p.deleteDirectoryRecursively(client, dirPath)
@@ -267,7 +267,7 @@ func (p *TopicRetentionPurger) deleteDirectoryRecursively(client filer_pb.Seawee
if resp.Entry == nil {
continue
}
entryPath := filepath.Join(dirPath, resp.Entry.Name)
entryPath := path.Join(dirPath, resp.Entry.Name)
if resp.Entry.IsDirectory {
// Recursively delete subdirectory
@@ -288,8 +288,8 @@ func (p *TopicRetentionPurger) deleteDirectoryRecursively(client filer_pb.Seawee
}
// Delete the directory itself
parentDir := filepath.Dir(dirPath)
dirName := filepath.Base(dirPath)
parentDir := path.Dir(dirPath)
dirName := path.Base(dirPath)
_, err = client.DeleteEntry(context.Background(), &filer_pb.DeleteEntryRequest{
Directory: parentDir,
+14 -15
View File
@@ -11,7 +11,6 @@ import (
"net/http"
"os"
"path"
"path/filepath"
"strconv"
"strings"
"time"
@@ -111,8 +110,8 @@ func (h *FileBrowserHandlers) DeleteFile(w http.ResponseWriter, r *http.Request)
// Delete file via filer
err := h.adminServer.WithFilerClient(func(client filer_pb.SeaweedFilerClient) error {
_, err := client.DeleteEntry(context.Background(), &filer_pb.DeleteEntryRequest{
Directory: filepath.Dir(request.Path),
Name: filepath.Base(request.Path),
Directory: path.Dir(request.Path),
Name: path.Base(request.Path),
IsDeleteData: true,
IsRecursive: true,
IgnoreRecursiveError: false,
@@ -143,8 +142,8 @@ func (h *FileBrowserHandlers) DeleteMultipleFiles(w http.ResponseWriter, r *http
return
}
for _, path := range request.Paths {
if strings.TrimSpace(path) == "" {
for _, p := range request.Paths {
if strings.TrimSpace(p) == "" {
writeJSONError(w, http.StatusBadRequest, "path is required")
return
}
@@ -155,11 +154,11 @@ func (h *FileBrowserHandlers) DeleteMultipleFiles(w http.ResponseWriter, r *http
var errors []string
// Delete each file/folder
for _, path := range request.Paths {
for _, p := range request.Paths {
err := h.adminServer.WithFilerClient(func(client filer_pb.SeaweedFilerClient) error {
_, err := client.DeleteEntry(context.Background(), &filer_pb.DeleteEntryRequest{
Directory: filepath.Dir(path),
Name: filepath.Base(path),
Directory: path.Dir(p),
Name: path.Base(p),
IsDeleteData: true,
IsRecursive: true,
IgnoreRecursiveError: false,
@@ -169,7 +168,7 @@ func (h *FileBrowserHandlers) DeleteMultipleFiles(w http.ResponseWriter, r *http
if err != nil {
failedCount++
errors = append(errors, fmt.Sprintf("%s: %v", path, err))
errors = append(errors, fmt.Sprintf("%s: %v", p, err))
} else {
deletedCount++
}
@@ -230,9 +229,9 @@ func (h *FileBrowserHandlers) CreateFolder(w http.ResponseWriter, r *http.Reques
// Create folder via filer
err := h.adminServer.WithFilerClient(func(client filer_pb.SeaweedFilerClient) error {
_, err := client.CreateEntry(context.Background(), &filer_pb.CreateEntryRequest{
Directory: filepath.Dir(fullPath),
Directory: path.Dir(fullPath),
Entry: &filer_pb.Entry{
Name: filepath.Base(fullPath),
Name: path.Base(fullPath),
IsDirectory: true,
Attributes: &filer_pb.FuseAttributes{
FileMode: uint32(0o755 | os.ModeDir), // Directory mode
@@ -444,8 +443,8 @@ func (h *FileBrowserHandlers) ViewFile(w http.ResponseWriter, r *http.Request) {
var fileEntry dash.FileEntry
err := h.adminServer.WithFilerClient(func(client filer_pb.SeaweedFilerClient) error {
resp, err := client.LookupDirectoryEntry(context.Background(), &filer_pb.LookupDirectoryEntryRequest{
Directory: filepath.Dir(filePath),
Name: filepath.Base(filePath),
Directory: path.Dir(filePath),
Name: path.Base(filePath),
})
if err != nil {
return err
@@ -549,8 +548,8 @@ func (h *FileBrowserHandlers) GetFileProperties(w http.ResponseWriter, r *http.R
var properties map[string]interface{}
err := h.adminServer.WithFilerClient(func(client filer_pb.SeaweedFilerClient) error {
resp, err := client.LookupDirectoryEntry(context.Background(), &filer_pb.LookupDirectoryEntryRequest{
Directory: filepath.Dir(filePath),
Name: filepath.Base(filePath),
Directory: path.Dir(filePath),
Name: path.Base(filePath),
})
if err != nil {
return err
+2 -2
View File
@@ -3,7 +3,7 @@ package sftpd
import (
"fmt"
"os"
"path/filepath"
stdpath "path"
"strings"
"github.com/seaweedfs/seaweedfs/weed/glog"
@@ -66,7 +66,7 @@ func (fs *SftpServer) CheckFilePermission(path string, perm string) error {
// If the path doesn't exist and we're checking for create/write/mkdir permission,
// check permissions on the parent directory instead
if err == os.ErrNotExist {
parentPath := filepath.Dir(path)
parentPath := stdpath.Dir(path)
// Check if user can write to the parent directory
return fs.CheckFilePermission(parentPath, perm)
}
+2 -2
View File
@@ -3,7 +3,7 @@ package user
import (
"math/rand/v2"
"path/filepath"
"path"
)
// User represents an SFTP user with authentication and permission details
@@ -27,7 +27,7 @@ func NewUser(username string) *User {
return &User{
Username: username,
Permissions: make(map[string][]string),
HomeDir: filepath.Join("/home", username),
HomeDir: path.Join("/home", username),
Uid: uint32(randomId),
Gid: uint32(randomId),
}