diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index d55ce50a6..3bf43744f 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -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 diff --git a/go.mod b/go.mod index 6b5e3a2fd..c695217d3 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index a2f0226dc..7521b8fe6 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/weed/mount/dir_sink.go b/weed/mount/dir_sink.go new file mode 100644 index 000000000..81cb6cd3a --- /dev/null +++ b/weed/mount/dir_sink.go @@ -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) +} diff --git a/weed/mount/posix_file_lock.go b/weed/mount/posix_file_lock.go index abe5f6fd9..425083f5e 100644 --- a/weed/mount/posix_file_lock.go +++ b/weed/mount/posix_file_lock.go @@ -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) } diff --git a/weed/mount/syscall_shim_unix.go b/weed/mount/syscall_shim_unix.go new file mode 100644 index 000000000..acb5f1d77 --- /dev/null +++ b/weed/mount/syscall_shim_unix.go @@ -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 +) diff --git a/weed/mount/syscall_shim_windows.go b/weed/mount/syscall_shim_windows.go new file mode 100644 index 000000000..aa4e29b53 --- /dev/null +++ b/weed/mount/syscall_shim_windows.go @@ -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 +) diff --git a/weed/mount/weedfs_access.go b/weed/mount/weedfs_access.go index 90890d823..27fa83132 100644 --- a/weed/mount/weedfs_access.go +++ b/weed/mount/weedfs_access.go @@ -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: diff --git a/weed/mount/weedfs_attr_windows.go b/weed/mount/weedfs_attr_windows.go new file mode 100644 index 000000000..044cc5fff --- /dev/null +++ b/weed/mount/weedfs_attr_windows.go @@ -0,0 +1,8 @@ +package mount + +import ( + "github.com/seaweedfs/go-fuse/v2/fuse" +) + +func setBlksize(out *fuse.Attr, size uint32) { +} diff --git a/weed/mount/weedfs_dir_read.go b/weed/mount/weedfs_dir_read.go index 488a69f38..dbdba0e97 100644 --- a/weed/mount/weedfs_dir_read.go +++ b/weed/mount/weedfs_dir_read.go @@ -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 { diff --git a/weed/mount/weedfs_file_lock.go b/weed/mount/weedfs_file_lock.go index 0e67d2fd5..b593bf72a 100644 --- a/weed/mount/weedfs_file_lock.go +++ b/weed/mount/weedfs_file_lock.go @@ -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) diff --git a/weed/mount/weedfs_posix_lock_routed.go b/weed/mount/weedfs_posix_lock_routed.go index 502ec8f3b..17c47b4b3 100644 --- a/weed/mount/weedfs_posix_lock_routed.go +++ b/weed/mount/weedfs_posix_lock_routed.go @@ -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 } diff --git a/weed/mount/weedfs_xattr.go b/weed/mount/weedfs_xattr.go index 8ae6eb6d5..a042c0e00 100644 --- a/weed/mount/weedfs_xattr.go +++ b/weed/mount/weedfs_xattr.go @@ -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 }