diff --git a/weed/mount/syscall_shim_windows_test.go b/weed/mount/syscall_shim_windows_test.go new file mode 100644 index 000000000..100d52557 --- /dev/null +++ b/weed/mount/syscall_shim_windows_test.go @@ -0,0 +1,16 @@ +package mount + +import "testing" + +// The Windows adapter forwards cgofuse's xattr flags to SetXAttr untouched, +// which only holds while both sides number them the same. cgofuse's side is +// pinned in weed/mount/winfsp; this pins ours, so editing either alone fails +// rather than silently turning a create into a replace. +func TestXattrFlagValues(t *testing.T) { + if xattr_CREATE != 1 { + t.Errorf("xattr_CREATE = %d, want 1", xattr_CREATE) + } + if xattr_REPLACE != 2 { + t.Errorf("xattr_REPLACE = %d, want 2", xattr_REPLACE) + } +} diff --git a/weed/mount/winfsp/errno_windows_test.go b/weed/mount/winfsp/errno_windows_test.go index 500b2cf07..7d553bc5a 100644 --- a/weed/mount/winfsp/errno_windows_test.go +++ b/weed/mount/winfsp/errno_windows_test.go @@ -76,3 +76,12 @@ func TestOpenFlagTranslation(t *testing.T) { t.Errorf("access mode not preserved: %#x", got) } } + +// Setxattr passes cgofuse's flags straight to the raw filesystem, which is +// only correct while the two number them identically. The other side is +// pinned by TestXattrFlagValues in weed/mount, so editing either alone fails. +func TestXattrFlagValues(t *testing.T) { + if cgofuse.XATTR_CREATE != 1 || cgofuse.XATTR_REPLACE != 2 { + t.Fatalf("cgofuse xattr flags moved: create=%d replace=%d", cgofuse.XATTR_CREATE, cgofuse.XATTR_REPLACE) + } +} diff --git a/weed/mount/winfsp/xattr_windows.go b/weed/mount/winfsp/xattr_windows.go new file mode 100644 index 000000000..f25043844 --- /dev/null +++ b/weed/mount/winfsp/xattr_windows.go @@ -0,0 +1,90 @@ +package winfsp + +import ( + "bytes" + + "github.com/seaweedfs/go-fuse/v2/fuse" +) + +// xattrBufferSize bounds one attribute value and one name listing. The mount +// stores extended attributes in the entry itself, so they are small by +// construction; this is the ceiling WinFsp will accept in a single reply. +const xattrBufferSize = 64 * 1024 + +func (w *WinFS) Getxattr(path string, name string) (int, []byte) { + inode, ref, status := w.resolve(path) + if status != fuse.OK { + return toErrno(status), nil + } + defer ref.release() + + dest := make([]byte, xattrBufferSize) + size, status := w.wfs.GetXAttr(never, ptr(w.caller(inode)), name, dest) + if status != fuse.OK { + return toErrno(status), nil + } + return 0, dest[:size] +} + +func (w *WinFS) Setxattr(path string, name string, value []byte, flags int) int { + if w.denied() { + return -eROFS + } + inode, ref, status := w.resolve(path) + if status != fuse.OK { + return toErrno(status) + } + defer ref.release() + + // cgofuse and the raw filesystem number XATTR_CREATE and XATTR_REPLACE the + // same, so the flags pass straight through; TestXattrFlagValues pins that. + in := &fuse.SetXAttrIn{InHeader: w.caller(inode)} + in.Flags = uint32(flags) + in.Size = uint32(len(value)) + return toErrno(w.wfs.SetXAttr(never, in, name, value)) +} + +func (w *WinFS) Removexattr(path string, name string) int { + if w.denied() { + return -eROFS + } + inode, ref, status := w.resolve(path) + if status != fuse.OK { + return toErrno(status) + } + defer ref.release() + return toErrno(w.wfs.RemoveXAttr(never, ptr(w.caller(inode)), name)) +} + +func (w *WinFS) Listxattr(path string, fill func(name string) bool) int { + inode, ref, status := w.resolve(path) + if status != fuse.OK { + return toErrno(status) + } + defer ref.release() + + dest := make([]byte, xattrBufferSize) + size, status := w.wfs.ListXAttr(never, ptr(w.caller(inode)), dest) + if status != fuse.OK { + return toErrno(status) + } + for _, name := range splitXattrNames(dest[:size]) { + if !fill(name) { + break + } + } + return 0 +} + +// splitXattrNames unpacks the NUL-separated listing the raw filesystem +// produces. A trailing NUL terminates the last name rather than starting an +// empty one. +func splitXattrNames(packed []byte) []string { + var names []string + for _, name := range bytes.Split(packed, []byte{0}) { + if len(name) > 0 { + names = append(names, string(name)) + } + } + return names +}