mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-08-16 12:16:36 +00:00
* mount: tell windows about changes made elsewhere Nothing invalidates a Windows client's cache from this side, so a file created or removed by another mount, the S3 gateway or the filer API stayed invisible in Explorer until the user refreshed by hand. The mount already receives those events; they just had nowhere to go. WFS gains a listener for every applied metadata event, and on Windows that turns into the WinFsp notification for the path. A rename reports both ends, since the destination's own event may never arrive when it falls outside this mount. * mount: report a removed directory as a directory Entry is nil once a path is vacated, so asking it whether the thing that went away was a directory always answered no and every removal was reported as a file. Windows watches the two through different filters, so a folder removed elsewhere never refreshed. The invalidation now carries what used to be there, which the event already knew and simply was not passing on. * mount: report a rename destination once The event stream already carries a second invalidation describing the new path, so reporting RenamedTo here sent the destination twice — and always as a create, so a moved directory arrived as a create followed by a mkdir.
193 lines
6.4 KiB
Go
193 lines
6.4 KiB
Go
package command
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/glog"
|
|
"github.com/seaweedfs/seaweedfs/weed/mount/meta_cache"
|
|
"github.com/seaweedfs/seaweedfs/weed/mount/winfsp"
|
|
"github.com/seaweedfs/seaweedfs/weed/util"
|
|
"github.com/seaweedfs/seaweedfs/weed/util/grace"
|
|
"github.com/seaweedfs/seaweedfs/weed/util/version"
|
|
)
|
|
|
|
// ownedByMounter reports the mount root as belonging to whoever started it,
|
|
// matching the uid=-1 option handed to WinFsp. It is a display value only:
|
|
// WinFsp substitutes the calling user, and it must never be persisted, since
|
|
// every other client would read the entry as owned by uid 4294967295.
|
|
const ownedByMounter = ^uint32(0)
|
|
|
|
// windowsAttrTimeoutSec bounds how long WinFsp may serve cached attributes.
|
|
// Nothing invalidates that cache from this side, so it stays short rather than
|
|
// borrowing an unrelated flag's value.
|
|
const windowsAttrTimeoutSec = 1.0
|
|
|
|
func RunMount(option *MountOptions, umask os.FileMode) bool {
|
|
chunkSizeLimitMB := *mountOptions.chunkSizeLimitMB
|
|
if chunkSizeLimitMB <= 0 {
|
|
fmt.Printf("Please specify a reasonable buffer size.\n")
|
|
return false
|
|
}
|
|
|
|
dir := *option.dir
|
|
if dir == "" {
|
|
fmt.Printf("Please specify the mount point via \"-dir\", for example -dir=S:\n")
|
|
return false
|
|
}
|
|
// A drive letter or a not-yet-existing directory is what WinFsp wants, so
|
|
// the mount point deliberately goes through none of the unix preparation:
|
|
// no ResolvePath, no auto-create, no stat.
|
|
if err := checkWindowsMountPoint(dir); err != nil {
|
|
fmt.Printf("%v\n", err)
|
|
return false
|
|
}
|
|
|
|
filerAddresses, grpcDialOption, cipher, bucketRootPath, ok := connectToFiler(option)
|
|
if !ok {
|
|
return true
|
|
}
|
|
|
|
if *option.localSocket == "" {
|
|
mountDirHash := util.HashToInt32([]byte(dir))
|
|
if mountDirHash < 0 {
|
|
mountDirHash = -mountDirHash
|
|
}
|
|
*option.localSocket = filepath.Join(os.TempDir(), fmt.Sprintf("seaweedfs-mount-%d.sock", mountDirHash))
|
|
}
|
|
if err := os.Remove(*option.localSocket); err != nil && !os.IsNotExist(err) {
|
|
glog.Fatalf("Failed to remove %s, error: %s", *option.localSocket, err.Error())
|
|
}
|
|
mountSocketListener, err := net.Listen("unix", *option.localSocket)
|
|
if err != nil {
|
|
glog.Fatalf("Failed to listen on %s: %v", *option.localSocket, err)
|
|
}
|
|
|
|
uidGidMapper, err := meta_cache.NewUidGidMapper(*option.uidMap, *option.gidMap)
|
|
if err != nil {
|
|
fmt.Printf("failed to parse %s %s: %v\n", *option.uidMap, *option.gidMap, err)
|
|
return false
|
|
}
|
|
|
|
mountRoot := resolveMountRoot(*option.filerMountRootPath)
|
|
cacheDirForRead, cacheDirForWrite := resolveCacheDirs(option)
|
|
|
|
seaweedFileSystem := buildSeaweedFileSystem(option, fileSystemParams{
|
|
dir: dir,
|
|
mountRoot: mountRoot,
|
|
filerAddresses: filerAddresses,
|
|
grpcDialOption: grpcDialOption,
|
|
cipher: cipher,
|
|
uidGidMapper: uidGidMapper,
|
|
uid: uint32(*option.windowsUid),
|
|
gid: uint32(*option.windowsGid),
|
|
mountMode: os.ModeDir | 0777,
|
|
mountCtime: time.Now(),
|
|
umask: umask,
|
|
chunkSizeLimitMB: chunkSizeLimitMB,
|
|
cacheDirForRead: cacheDirForRead,
|
|
cacheDirForWrite: cacheDirForWrite,
|
|
})
|
|
|
|
if !createMountRoot(seaweedFileSystem, mountRoot, bucketRootPath, filerAddresses) {
|
|
return false
|
|
}
|
|
|
|
host := winfsp.New(seaweedFileSystem, winfsp.Options{
|
|
VolumeName: strings.ReplaceAll(*option.filer, ",", "+"),
|
|
Uid: ownedByMounter,
|
|
Gid: ownedByMounter,
|
|
AttrTimeout: windowsAttrTimeoutSec,
|
|
ReadOnly: *option.readOnly,
|
|
Debug: *option.debugFuse,
|
|
ExtraOptions: option.extraOptions,
|
|
})
|
|
|
|
// Windows caches entries on its own side and the mount cannot invalidate
|
|
// that cache directly, so changes made elsewhere have to be pushed out.
|
|
host.Notify(seaweedFileSystem)
|
|
|
|
grace.OnInterrupt(func() {
|
|
// The signal handler exits the process as soon as the hooks return, so
|
|
// anything still queued has to be flushed here rather than after Serve.
|
|
// WaitForAsyncFlush is idempotent, so the post-Serve call below is
|
|
// harmless if both run.
|
|
host.Unmount()
|
|
seaweedFileSystem.WaitForAsyncFlush()
|
|
})
|
|
|
|
serveMountGrpc(seaweedFileSystem, mountSocketListener)
|
|
|
|
if err := seaweedFileSystem.StartBackgroundTasks(); err != nil {
|
|
fmt.Printf("failed to start background tasks: %v\n", err)
|
|
return false
|
|
}
|
|
|
|
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 err := host.Serve(windowsMountPoint(dir)); err != nil {
|
|
glog.Errorf("%v", err)
|
|
return false
|
|
}
|
|
|
|
seaweedFileSystem.WaitForAsyncFlush()
|
|
seaweedFileSystem.ClearCacheDir()
|
|
|
|
return true
|
|
}
|
|
|
|
// checkWindowsMountPoint rejects the mount points WinFsp cannot take, which is
|
|
// worth doing up front because its own failure is a bare false.
|
|
func checkWindowsMountPoint(dir string) error {
|
|
if isDriveLetter(dir) {
|
|
if _, err := os.Stat(strings.TrimRight(dir, `\/`) + `\`); err == nil {
|
|
return fmt.Errorf("drive %s is already in use", dir)
|
|
}
|
|
return nil
|
|
}
|
|
if strings.HasPrefix(dir, `\\`) {
|
|
return nil
|
|
}
|
|
|
|
// WinFsp creates the directory itself, with FILE_CREATE, and deletes it
|
|
// again when the filesystem goes away. An existing one — empty or not —
|
|
// fails with "mount point in use".
|
|
if _, err := os.Stat(dir); err == nil {
|
|
return fmt.Errorf("mount point %s already exists; WinFsp creates the directory itself, so give it a path that does not exist yet", dir)
|
|
} else if !os.IsNotExist(err) {
|
|
return err
|
|
}
|
|
if _, err := os.Stat(filepath.Dir(dir)); err != nil {
|
|
return fmt.Errorf("parent of mount point %s does not exist", dir)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// windowsMountPoint drops a trailing separator from a drive letter. WinFsp
|
|
// recognises a drive only as exactly two characters; "S:\\" falls through to
|
|
// its directory handling and the mount fails.
|
|
func windowsMountPoint(dir string) string {
|
|
if isDriveLetter(dir) {
|
|
return strings.TrimRight(dir, `\/`)
|
|
}
|
|
return dir
|
|
}
|
|
|
|
// isDriveLetter accepts both "S:" and "S:\\"; the trailing separator is how
|
|
// the drive is usually written, and windowsMountPoint normalises it.
|
|
func isDriveLetter(dir string) bool {
|
|
trimmed := strings.TrimRight(dir, `\/`)
|
|
if len(trimmed) != 2 || trimmed[1] != ':' {
|
|
return false
|
|
}
|
|
c := trimmed[0]
|
|
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')
|
|
}
|