mount: build the package on windows (#10535)

* mount: drop the unused go-fuse fs package dependency

WFS embedded fs.Inode but never used any of its methods, and the only
other reference was RENAME_EXCHANGE, a constant sitting next to three
literals. Removing both drops fs and five internal packages from the
mount build graph.

* mount: build the package on windows

Windows has no fcntl lock types, no O_ACCMODE and no x/sys/unix, so a
handful of constants kept weed/mount pinned to unix even though the code
using them is portable in-memory logic. Route them through per-OS shims
and give setBlksize a windows no-op.

The POSIX lock table now compiles on windows but stays unreachable:
WinFsp resolves byte-range locks in its own kernel driver, so nothing
will feed it there.

go.mod points at a go-fuse branch commit and needs repinning to a release
tag once that lands.

* ci: cross-compile for windows

Nothing caught the unix-only constants creeping into weed/mount until a
release build failed.

* mount: let readdir feed a sink instead of the kernel buffer

doReadDirectory wrote directly into fuse.DirEntryList, which is the
kernel's wire format. A front end that is not the kernel would have to
pack entries only to parse them straight back out.

Route it through DirEntrySink instead. ReadDir and ReadDirPlus pass the
reply buffer, so nothing changes for the FUSE server.

* mount: pin go-fuse v2.9.4 for the windows build
This commit is contained in:
Chris Lu
2026-08-03 01:07:49 -07:00
committed by GitHub
parent 529ffa5c86
commit 4f692bf9c3
13 changed files with 126 additions and 32 deletions
+15
View File
@@ -75,6 +75,21 @@ jobs:
- name: Build
run: cd weed; go build -tags "elastic gocdk sqlite ydb tarantool tikv rclone" -v .
build-windows:
name: Build windows
runs-on: ubuntu-latest
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v7
- name: Set up Go
uses: actions/setup-go@v7
with:
go-version-file: 'go.mod'
# weed/mount builds here but cannot mount yet. Without this the unix-only
# syscall constants it needs creep back in unnoticed.
- name: Build windows/amd64
run: GOOS=windows GOARCH=amd64 go build ./weed/...
test:
name: Test
runs-on: ubuntu-latest
+1 -1
View File
@@ -148,7 +148,7 @@ require (
github.com/rdleal/intervalst v1.5.0
github.com/redis/go-redis/v9 v9.21.0
github.com/schollz/progressbar/v3 v3.19.1
github.com/seaweedfs/go-fuse/v2 v2.9.3
github.com/seaweedfs/go-fuse/v2 v2.9.4
github.com/shirou/gopsutil/v4 v4.26.6
github.com/tarantool/go-option v1.1.0
github.com/tarantool/go-tarantool/v3 v3.0.0
+6
View File
@@ -1810,6 +1810,12 @@ github.com/seaweedfs/cockroachdb-parser v0.0.0-20260225204133-2f342c5ea564 h1:Tg
github.com/seaweedfs/cockroachdb-parser v0.0.0-20260225204133-2f342c5ea564/go.mod h1:JSKCh6uCHBz91lQYFYHCyTrSVIPge4SUFVn28iwMNB0=
github.com/seaweedfs/go-fuse/v2 v2.9.3 h1:rJufGrHImTx7yoGUmetUi+To4LrmTQJROJnBHWt92ic=
github.com/seaweedfs/go-fuse/v2 v2.9.3/go.mod h1:zABdmWEa6A0bwaBeEOBUeUkGIZlxUhcdv+V1Dcc/U/I=
github.com/seaweedfs/go-fuse/v2 v2.9.4-0.20260803073934-cb95bfebced6 h1:Kn79NPV77uc3bEat4RWi6vpNYNacYfjXcWBLNv3RZ2A=
github.com/seaweedfs/go-fuse/v2 v2.9.4-0.20260803073934-cb95bfebced6/go.mod h1:zABdmWEa6A0bwaBeEOBUeUkGIZlxUhcdv+V1Dcc/U/I=
github.com/seaweedfs/go-fuse/v2 v2.9.4-0.20260803074752-bd46d45ccb3b h1:ECjScJHzIATk8CTdlP+yfPY4W5bLxeoy1Mma8rd0dRA=
github.com/seaweedfs/go-fuse/v2 v2.9.4-0.20260803074752-bd46d45ccb3b/go.mod h1:zABdmWEa6A0bwaBeEOBUeUkGIZlxUhcdv+V1Dcc/U/I=
github.com/seaweedfs/go-fuse/v2 v2.9.4 h1:ACyloiuopdhRSjdLLeSWbsVaemMPskORaRF01TY6GyM=
github.com/seaweedfs/go-fuse/v2 v2.9.4/go.mod h1:zABdmWEa6A0bwaBeEOBUeUkGIZlxUhcdv+V1Dcc/U/I=
github.com/seaweedfs/goexif v1.0.3 h1:ve/OjI7dxPW8X9YQsv3JuVMaxEyF9Rvfd04ouL+Bz30=
github.com/seaweedfs/goexif v1.0.3/go.mod h1:Oni780Z236sXpIQzk1XoJlTwqrJ02smEin9zQeff7Fk=
github.com/seaweedfs/raft v1.2.0 h1:Ez4Hw9ifBbTT7wg54DvGHBjw1vRlTb4roH0TKl0Oj9Y=
+37
View File
@@ -0,0 +1,37 @@
package mount
import (
"github.com/seaweedfs/go-fuse/v2/fuse"
)
// DirEntrySink receives the entries a readdir produces. The FUSE server packs
// them straight into the kernel's reply buffer; a front end that is not the
// kernel reads them out instead of re-parsing that wire format.
type DirEntrySink interface {
// AddEntry reports one entry, returning false once the sink is full. A
// full sink ends the batch; the client resumes from the entry's Off.
AddEntry(entry fuse.DirEntry) bool
// AddEntryPlus is AddEntry for readdirplus, returning the attribute block
// to fill in, or nil once the sink is full.
AddEntryPlus(entry fuse.DirEntry) *fuse.EntryOut
}
// fuseDirEntryList adapts the kernel reply buffer to DirEntrySink.
type fuseDirEntryList struct {
*fuse.DirEntryList
}
func (l fuseDirEntryList) AddEntry(entry fuse.DirEntry) bool {
return l.AddDirEntry(entry)
}
func (l fuseDirEntryList) AddEntryPlus(entry fuse.DirEntry) *fuse.EntryOut {
return l.AddDirLookupEntry(entry)
}
// ReadDirectoryInto runs a readdir against sink. ReadDir and ReadDirPlus are
// this with the kernel reply buffer as the sink.
func (wfs *WFS) ReadDirectoryInto(input *fuse.ReadIn, sink DirEntrySink, isPlusMode bool) fuse.Status {
return wfs.doReadDirectory(input, sink, isPlusMode)
}
+6 -7
View File
@@ -4,7 +4,6 @@ import (
"math"
"sort"
"sync"
"syscall"
"github.com/seaweedfs/go-fuse/v2/fuse"
)
@@ -13,7 +12,7 @@ import (
type lockRange struct {
Start uint64 // inclusive byte offset
End uint64 // inclusive; math.MaxUint64 means "to EOF"
Typ uint32 // syscall.F_RDLCK or syscall.F_WRLCK
Typ uint32 // f_RDLCK or f_WRLCK
Owner uint64 // FUSE lock owner (from LkIn.Owner)
Pid uint32 // PID of lock holder (for GetLk reporting)
// flock and fcntl locks have different ownership and close semantics.
@@ -127,7 +126,7 @@ func findConflict(locks []lockRange, proposed lockRange) (lockRange, bool) {
if !rangesOverlap(h.Start, h.End, proposed.Start, proposed.End) {
continue
}
if h.Typ == syscall.F_RDLCK && proposed.Typ == syscall.F_RDLCK {
if h.Typ == f_RDLCK && proposed.Typ == f_RDLCK {
continue
}
return h, true
@@ -304,7 +303,7 @@ func (plt *PosixLockTable) GetLk(inode uint64, proposed lockRange, out *fuse.LkO
for {
il := plt.getInodeLocks(inode)
if il == nil {
out.Lk.Typ = syscall.F_UNLCK
out.Lk.Typ = f_UNLCK
return
}
il.mu.Lock()
@@ -324,7 +323,7 @@ func (plt *PosixLockTable) GetLk(inode uint64, proposed lockRange, out *fuse.LkO
out.Lk.Typ = conflict.Typ
out.Lk.Pid = conflict.Pid
} else {
out.Lk.Typ = syscall.F_UNLCK
out.Lk.Typ = f_UNLCK
}
return
}
@@ -334,7 +333,7 @@ func (plt *PosixLockTable) GetLk(inode uint64, proposed lockRange, out *fuse.LkO
// For unlock (F_UNLCK): removes locks in the given range for the owner.
// For lock: returns fuse.EAGAIN if a conflict exists, fuse.OK on success.
func (plt *PosixLockTable) SetLk(inode uint64, lk lockRange) fuse.Status {
if lk.Typ == syscall.F_UNLCK {
if lk.Typ == f_UNLCK {
il := plt.getInodeLocks(inode)
if il == nil {
return fuse.OK
@@ -376,7 +375,7 @@ func (plt *PosixLockTable) SetLk(inode uint64, lk lockRange) fuse.Status {
// SetLkw attempts a blocking lock. It waits until the lock can be acquired
// or the cancel channel is closed.
func (plt *PosixLockTable) SetLkw(inode uint64, lk lockRange, cancel <-chan struct{}) fuse.Status {
if lk.Typ == syscall.F_UNLCK {
if lk.Typ == f_UNLCK {
return plt.SetLk(inode, lk)
}
+19
View File
@@ -0,0 +1,19 @@
//go:build !windows
package mount
import (
"syscall"
sys "golang.org/x/sys/unix"
)
const (
f_RDLCK = syscall.F_RDLCK
f_WRLCK = syscall.F_WRLCK
f_UNLCK = syscall.F_UNLCK
o_ACCMODE = syscall.O_ACCMODE
xattr_CREATE = sys.XATTR_CREATE
xattr_REPLACE = sys.XATTR_REPLACE
)
+14
View File
@@ -0,0 +1,14 @@
package mount
// Windows has no fcntl lock types and no access-mode mask. The Linux values
// stand in: nothing on Windows feeds these, and the lock table only ever
// compares them against each other.
const (
f_RDLCK = 0
f_WRLCK = 1
f_UNLCK = 2
o_ACCMODE = 0x3
xattr_CREATE = 1
xattr_REPLACE = 2
)
+1 -1
View File
@@ -155,7 +155,7 @@ func checkStickyBit(dirMode, dirUid, targetUid, callerUid uint32) fuse.Status {
// openFlagsToAccessMask converts open(2) flags to an access permission mask.
func openFlagsToAccessMask(flags uint32) uint32 {
switch flags & uint32(syscall.O_ACCMODE) {
switch flags & uint32(o_ACCMODE) {
case syscall.O_WRONLY:
return fuse.W_OK
case syscall.O_RDWR:
+8
View File
@@ -0,0 +1,8 @@
package mount
import (
"github.com/seaweedfs/go-fuse/v2/fuse"
)
func setBlksize(out *fuse.Attr, size uint32) {
}
+10 -10
View File
@@ -139,14 +139,14 @@ func (wfs *WFS) FsyncDir(cancel <-chan struct{}, input *fuse.FsyncIn) (code fuse
* '1'.
*/
func (wfs *WFS) ReadDir(cancel <-chan struct{}, input *fuse.ReadIn, out *fuse.DirEntryList) (code fuse.Status) {
return wfs.doReadDirectory(input, out, false)
return wfs.doReadDirectory(input, fuseDirEntryList{out}, false)
}
func (wfs *WFS) ReadDirPlus(cancel <-chan struct{}, input *fuse.ReadIn, out *fuse.DirEntryList) (code fuse.Status) {
return wfs.doReadDirectory(input, out, true)
return wfs.doReadDirectory(input, fuseDirEntryList{out}, true)
}
func (wfs *WFS) doReadDirectory(input *fuse.ReadIn, out *fuse.DirEntryList, isPlusMode bool) fuse.Status {
func (wfs *WFS) doReadDirectory(input *fuse.ReadIn, out DirEntrySink, isPlusMode bool) fuse.Status {
// Get the directory handle and lock it for the duration of this operation.
// This serializes concurrent readdir calls on the same handle, fixing the
// race condition that caused hangs with NFS-Ganesha.
@@ -182,11 +182,11 @@ func (wfs *WFS) doReadDirectory(input *fuse.ReadIn, out *fuse.DirEntryList, isPl
dirEntry.Off = dh.entryStreamOffset + uint64(index) + 1
if !isPlusMode {
if !out.AddDirEntry(dirEntry) {
if !out.AddEntry(dirEntry) {
return false
}
} else {
entryOut := out.AddDirLookupEntry(dirEntry)
entryOut := out.AddEntryPlus(dirEntry)
if entryOut == nil {
return false
}
@@ -203,14 +203,14 @@ func (wfs *WFS) doReadDirectory(input *fuse.ReadIn, out *fuse.DirEntryList, isPl
if input.Offset < directoryStreamBaseOffset {
if !isPlusMode {
if input.Offset == 0 {
out.AddDirEntry(fuse.DirEntry{Mode: fuse.S_IFDIR, Name: ".", Off: 1})
out.AddEntry(fuse.DirEntry{Mode: fuse.S_IFDIR, Name: ".", Off: 1})
}
out.AddDirEntry(fuse.DirEntry{Mode: fuse.S_IFDIR, Name: "..", Off: 2})
out.AddEntry(fuse.DirEntry{Mode: fuse.S_IFDIR, Name: "..", Off: 2})
} else {
if input.Offset == 0 {
out.AddDirLookupEntry(fuse.DirEntry{Mode: fuse.S_IFDIR, Name: ".", Off: 1})
out.AddEntryPlus(fuse.DirEntry{Mode: fuse.S_IFDIR, Name: ".", Off: 1})
}
out.AddDirLookupEntry(fuse.DirEntry{Mode: fuse.S_IFDIR, Name: "..", Off: 2})
out.AddEntryPlus(fuse.DirEntry{Mode: fuse.S_IFDIR, Name: "..", Off: 2})
}
input.Offset = directoryStreamBaseOffset
}
@@ -296,7 +296,7 @@ func (wfs *WFS) doReadDirectory(input *fuse.ReadIn, out *fuse.DirEntryList, isPl
return fuse.OK
}
func (wfs *WFS) readDirectoryDirect(input *fuse.ReadIn, out *fuse.DirEntryList, dh *DirectoryHandle, dirPath util.FullPath, processEachEntryFn func(entry *filer.Entry, index int64) bool) fuse.Status {
func (wfs *WFS) readDirectoryDirect(input *fuse.ReadIn, out DirEntrySink, dh *DirectoryHandle, dirPath util.FullPath, processEachEntryFn func(entry *filer.Entry, index int64) bool) fuse.Status {
var lastEntryName string
if input.Offset >= dh.entryStreamOffset {
+1 -3
View File
@@ -1,8 +1,6 @@
package mount
import (
"syscall"
"github.com/seaweedfs/go-fuse/v2/fuse"
)
@@ -56,7 +54,7 @@ func (wfs *WFS) SetLkw(cancel <-chan struct{}, in *fuse.LkIn) fuse.Status {
Pid: in.Lk.Pid,
IsFlock: in.LkFlags&fuse.FUSE_LK_FLOCK != 0,
}
if lk.Typ == syscall.F_UNLCK {
if lk.Typ == f_UNLCK {
return wfs.posixLocks.SetLk(in.NodeId, lk)
}
return wfs.posixLocks.SetLkw(in.NodeId, lk, cancel)
+6 -7
View File
@@ -6,7 +6,6 @@ import (
"encoding/hex"
"math/rand/v2"
"sync"
"syscall"
"time"
"github.com/seaweedfs/go-fuse/v2/fuse"
@@ -103,9 +102,9 @@ func (wfs *WFS) posixLockKeyForInode(inode uint64) (string, bool) {
func posixLockTypeToWire(typ uint32) uint32 {
switch typ {
case syscall.F_RDLCK:
case f_RDLCK:
return posixlock.Read
case syscall.F_WRLCK:
case f_WRLCK:
return posixlock.Write
default:
return posixlock.Unlock
@@ -115,11 +114,11 @@ func posixLockTypeToWire(typ uint32) uint32 {
func posixLockTypeFromWire(typ uint32) uint32 {
switch typ {
case posixlock.Read:
return syscall.F_RDLCK
return f_RDLCK
case posixlock.Write:
return syscall.F_WRLCK
return f_WRLCK
default:
return syscall.F_UNLCK
return f_UNLCK
}
}
@@ -186,7 +185,7 @@ func (wfs *WFS) routedGetLk(cancel <-chan struct{}, in *fuse.LkIn, out *fuse.LkO
out.Lk.Start, out.Lk.End, out.Lk.Pid = c.GetStart(), c.GetEnd(), c.GetPid()
out.Lk.Typ = posixLockTypeFromWire(c.GetType())
} else {
out.Lk.Typ = syscall.F_UNLCK
out.Lk.Typ = f_UNLCK
}
return fuse.OK
}
+2 -3
View File
@@ -8,7 +8,6 @@ import (
"syscall"
"github.com/seaweedfs/go-fuse/v2/fuse"
sys "golang.org/x/sys/unix"
)
const (
@@ -123,11 +122,11 @@ func (wfs *WFS) SetXAttr(cancel <-chan struct{}, input *fuse.SetXAttrIn, attr st
}
_, exists := entry.Extended[XATTR_PREFIX+attr]
switch input.Flags {
case sys.XATTR_CREATE:
case xattr_CREATE:
if exists {
return fuse.Status(syscall.EEXIST)
}
case sys.XATTR_REPLACE:
case xattr_REPLACE:
if !exists {
return fuse.ENODATA
}