mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-08-28 11:56:07 +00:00
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:
@@ -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 \
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user