mount: register UNC mount points as WinFsp network file systems (#10943)

A \\server\share -dir was passed to WinFsp as a plain mount point, which
treats it as a directory path on an actual remote server and fails. Turn it
into the VolumePrefix option instead, so the mount registers with the WinFsp
network provider: the UNC path is then reachable from every logon session,
which a drive letter mounted from a service is not, and each user can map
their own drive letter to it.
This commit is contained in:
Chris Lu
2026-08-25 01:28:50 -07:00
committed by GitHub
parent 40f77503d0
commit 68f0793b6f
5 changed files with 95 additions and 2 deletions
+7
View File
@@ -192,6 +192,13 @@ var cmdMount = &Command{
On OS X, it requires OSXFUSE (https://osxfuse.github.io/).
On Windows, it requires WinFsp (https://winfsp.dev/). The mount point can be
a drive letter (-dir=S:), a directory that does not exist yet, or a network
path (-dir=\\seaweedfs\share). A drive letter belongs to the logon session
that created it, so one mounted by a service is usually invisible to users
at the desktop; the network path form is reachable from every session, and
each user can map their own drive letter to it.
RDMA Acceleration:
For ultra-fast reads, enable RDMA acceleration with an RDMA sidecar:
weed mount -filer=localhost:8888 -dir=/mnt/seaweedfs \
+6
View File
@@ -136,6 +136,9 @@ func RunMount(option *MountOptions, umask os.FileMode) bool {
glog.V(0).Infof("mounting %s%s to %v", *option.filer, mountRoot, dir)
glog.V(0).Infof("This is SeaweedFS version %s %s %s", version.Version(), runtime.GOOS, runtime.GOARCH)
glog.V(0).Infof("Windows mount is beta: hard links are unavailable and byte-range locks are not shared across mounts")
if prefix, _ := winfsp.VolumePrefix(dir); prefix != "" {
glog.V(0).Infof(`network mount: every session on this machine can open \%s or map a drive letter to it`, prefix)
}
if err := host.Serve(windowsMountPoint(dir)); err != nil {
glog.Errorf("%v", err)
@@ -157,6 +160,9 @@ func checkWindowsMountPoint(dir string) error {
}
return nil
}
if prefix, err := winfsp.VolumePrefix(dir); err != nil || prefix != "" {
return err
}
if strings.HasPrefix(dir, `\\`) {
return nil
}
+13 -2
View File
@@ -65,9 +65,13 @@ func (h *Host) Notify(wfs *mount.WFS) {
}
// Serve attaches the filesystem at mountPoint, which is a drive letter ("S:"),
// a directory that does not yet exist, or a UNC path. It blocks until the
// filesystem is unmounted.
// a directory that does not yet exist, or a \\server\share UNC path. It blocks
// until the filesystem is unmounted.
func (h *Host) Serve(mountPoint string) error {
prefix, err := VolumePrefix(mountPoint)
if err != nil {
return err
}
opts := []string{
"-o", "volname=" + h.volumeName(),
"-o", "uid=-1",
@@ -99,6 +103,13 @@ func (h *Host) Serve(mountPoint string) error {
"-o", "EaTimeout="+ms,
)
}
if prefix != "" {
// The mount point itself becomes the network prefix; what WinFsp
// still wants a mount point for is the drive letter it gives the
// mounting session, and "*" lets it pick a free one.
opts = append(opts, "-o", "VolumePrefix="+prefix)
mountPoint = "*"
}
if h.options.Debug {
opts = append(opts, "-d")
}
+32
View File
@@ -0,0 +1,32 @@
package winfsp
import (
"fmt"
"strings"
)
// VolumePrefix converts a \\server\share mount point into WinFsp's
// VolumePrefix option, which registers the mount as a network file system.
// That registration is what makes the mount reachable from every logon
// session by its UNC path — a drive letter lives inside the session that
// created it, which is why a service mount is invisible to the users logged
// on at the desktop. Handed through as a plain mount point instead, the UNC
// path would be taken for a directory on an actual remote server and the
// mount would fail.
//
// Mount points that are not UNC paths return "", including the \\?\ and \\.\
// device forms, which WinFsp interprets itself.
func VolumePrefix(mountPoint string) (string, error) {
normalized := strings.ReplaceAll(mountPoint, "/", `\`)
if !strings.HasPrefix(normalized, `\\`) {
return "", nil
}
if strings.HasPrefix(normalized, `\\?\`) || strings.HasPrefix(normalized, `\\.\`) {
return "", nil
}
parts := strings.Split(strings.TrimRight(normalized[2:], `\`), `\`)
if len(parts) != 2 || parts[0] == "" || parts[1] == "" {
return "", fmt.Errorf(`a network mount point must be \\server\share, got %s`, mountPoint)
}
return `\` + parts[0] + `\` + parts[1], nil
}
+37
View File
@@ -0,0 +1,37 @@
package winfsp
import "testing"
func TestVolumePrefix(t *testing.T) {
tests := []struct {
name string
mountPoint string
prefix string
wantErr bool
}{
{name: "drive letter", mountPoint: `S:`, prefix: ""},
{name: "directory", mountPoint: `C:\mnt\weed`, prefix: ""},
{name: "relative directory", mountPoint: `mnt\weed`, prefix: ""},
{name: "unc", mountPoint: `\\seaweedfs\share`, prefix: `\seaweedfs\share`},
{name: "unc trailing separator", mountPoint: `\\seaweedfs\share\`, prefix: `\seaweedfs\share`},
{name: "unc forward slashes", mountPoint: `//seaweedfs/share`, prefix: `\seaweedfs\share`},
{name: "unc dotted server", mountPoint: `\\fs.example.com\share`, prefix: `\fs.example.com\share`},
{name: "mountmgr drive", mountPoint: `\\.\S:`, prefix: ""},
{name: "extended drive", mountPoint: `\\?\S:`, prefix: ""},
{name: "server only", mountPoint: `\\seaweedfs`, wantErr: true},
{name: "too deep", mountPoint: `\\seaweedfs\share\dir`, wantErr: true},
{name: "empty server", mountPoint: `\\\share`, wantErr: true},
{name: "empty share", mountPoint: `\\seaweedfs\\`, wantErr: true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
prefix, err := VolumePrefix(tt.mountPoint)
if (err != nil) != tt.wantErr {
t.Fatalf("VolumePrefix(%q) error = %v, wantErr %v", tt.mountPoint, err, tt.wantErr)
}
if prefix != tt.prefix {
t.Fatalf("VolumePrefix(%q) = %q, want %q", tt.mountPoint, prefix, tt.prefix)
}
})
}
}