Skip to content

fuse: remove the 1GiB buffer view limit#101

Open
chinasasu wants to merge 1 commit into
winfsp:masterfrom
chinasasu:fix/large-windows-buffers
Open

fuse: remove the 1GiB buffer view limit#101
chinasasu wants to merge 1 commit into
winfsp:masterfrom
chinasasu:fix/large-windows-buffers

Conversation

@chinasasu
Copy link
Copy Markdown

This replaces the fixed (*[1 << 30]byte)(unsafe.Pointer(...)) buffer view with an unsafe.Slice view sized to the actual request.

Why this change:

  • the current code path assumes that every C buffer passed into hostRead, hostWrite, hostSetxattr, hostGetxattr, hostListxattr, hostReadlink, and hostGetpath is at most 1 GiB long
  • when a caller issues a larger request, expressions like buff[:size0] can panic before the filesystem callback is reached
  • recoverAsErrno then converts that panic into -EIO, which surfaces as an unexpected I/O failure to the caller

What this patch does:

  • adds a small helper to construct a Go byte slice from the incoming C pointer and request length
  • uses that helper in the affected host entrypoints
  • preserves the existing zero-length behavior for string-returning helpers before indexing size0-1

Validation:

  • downstream Windows FUSE integration tests reproduced IO DEVICE ERROR for a single ReadFile request larger than 1 GiB before this change
  • with this patch applied, the same request reaches the filesystem read callback successfully
  • go test ./fuse passes locally

@chinasasu chinasasu force-pushed the fix/large-windows-buffers branch from bbe850d to 174d49c Compare March 23, 2026 04:59
@chinasasu chinasasu force-pushed the fix/large-windows-buffers branch from 174d49c to 9d910cf Compare March 23, 2026 05:04
@billziss-gh
Copy link
Copy Markdown
Collaborator

I understand that this may solve a real problem for you. However we still need to support 32-bit systems and this looks a bit complicated to me.

The expression ^uint(0)>>32 yields 0 on 32-bit systems and binary ...111 on 64-bit or higher (including future 128-bit) systems. Therefore the expression ^uint(0)>>32&1 yields 0 on 32-bit and 1 on 64-bit. Finally the expression 30 + 10*(^uint(0)>>32&1) yields 30 on 32-bit and 40 on 64-bit.

So perhaps the following change should do the trick. From:

buff := (*[1 << 30]byte)(unsafe.Pointer(buff0))

To:

buff := (*[1 << (30 + 10*(^uint(0)>>32&1))]byte)(unsafe.Pointer(buff0))

Full diff:

diff --git c/fuse/host.go i/fuse/host.go
index 083c03c..5176029 100644
--- c/fuse/host.go
+++ i/fuse/host.go
@@ -135,7 +135,7 @@ func hostReadlink(path0 *c_char, buff0 *c_char, size0 c_size_t) (errc0 c_int) {
 	fsop := hostHandleGet(c_fuse_get_context().private_data).fsop
 	path := c_GoString(path0)
 	errc, rslt := fsop.Readlink(path)
-	buff := (*[1 << 30]byte)(unsafe.Pointer(buff0))
+	buff := (*[1 << (30 + 10*(^uint(0)>>32&1))]byte)(unsafe.Pointer(buff0))
 	copy(buff[:size0-1], rslt)
 	rlen := len(rslt)
 	if c_size_t(rlen) < size0 {
@@ -284,7 +284,7 @@ func hostRead(path0 *c_char, buff0 *c_char, size0 c_size_t, ofst0 c_fuse_off_t,
 	defer recoverAsErrno(&nbyt0)
 	fsop := hostHandleGet(c_fuse_get_context().private_data).fsop
 	path := c_GoString(path0)
-	buff := (*[1 << 30]byte)(unsafe.Pointer(buff0))
+	buff := (*[1 << (30 + 10*(^uint(0)>>32&1))]byte)(unsafe.Pointer(buff0))
 	nbyt := fsop.Read(path, buff[:size0], int64(ofst0), uint64(fi0.fh))
 	return c_int(nbyt)
 }
@@ -294,7 +294,7 @@ func hostWrite(path0 *c_char, buff0 *c_char, size0 c_size_t, ofst0 c_fuse_off_t,
 	defer recoverAsErrno(&nbyt0)
 	fsop := hostHandleGet(c_fuse_get_context().private_data).fsop
 	path := c_GoString(path0)
-	buff := (*[1 << 30]byte)(unsafe.Pointer(buff0))
+	buff := (*[1 << (30 + 10*(^uint(0)>>32&1))]byte)(unsafe.Pointer(buff0))
 	nbyt := fsop.Write(path, buff[:size0], int64(ofst0), uint64(fi0.fh))
 	return c_int(nbyt)
 }
@@ -346,7 +346,7 @@ func hostSetxattr(path0 *c_char, name0 *c_char, buff0 *c_char, size0 c_size_t,
 	fsop := hostHandleGet(c_fuse_get_context().private_data).fsop
 	path := c_GoString(path0)
 	name := c_GoString(name0)
-	buff := (*[1 << 30]byte)(unsafe.Pointer(buff0))
+	buff := (*[1 << (30 + 10*(^uint(0)>>32&1))]byte)(unsafe.Pointer(buff0))
 	errc := fsop.Setxattr(path, name, buff[:size0], int(flags))
 	return c_int(errc)
 }
@@ -364,7 +364,7 @@ func hostGetxattr(path0 *c_char, name0 *c_char, buff0 *c_char, size0 c_size_t) (
 		if len(rslt) > int(size0) {
 			return -c_int(ERANGE)
 		}
-		buff := (*[1 << 30]byte)(unsafe.Pointer(buff0))
+		buff := (*[1 << (30 + 10*(^uint(0)>>32&1))]byte)(unsafe.Pointer(buff0))
 		copy(buff[:size0], rslt)
 	}
 	return c_int(len(rslt))
@@ -374,7 +374,7 @@ func hostListxattr(path0 *c_char, buff0 *c_char, size0 c_size_t) (nbyt0 c_int) {
 	defer recoverAsErrno(&nbyt0)
 	fsop := hostHandleGet(c_fuse_get_context().private_data).fsop
 	path := c_GoString(path0)
-	buff := (*[1 << 30]byte)(unsafe.Pointer(buff0))
+	buff := (*[1 << (30 + 10*(^uint(0)>>32&1))]byte)(unsafe.Pointer(buff0))
 	size := int(size0)
 	nbyt := 0
 	fill := func(name1 string) bool {
@@ -599,7 +599,7 @@ func hostGetpath(path0 *c_char, buff0 *c_char, size0 c_size_t,
 		fifh = uint64(fi0.fh)
 	}
 	errc, rslt := intf.Getpath(path, fifh)
-	buff := (*[1 << 30]byte)(unsafe.Pointer(buff0))
+	buff := (*[1 << (30 + 10*(^uint(0)>>32&1))]byte)(unsafe.Pointer(buff0))
 	copy(buff[:size0-1], rslt)
 	rlen := len(rslt)
 	if c_size_t(rlen) < size0 {

@billziss-gh
Copy link
Copy Markdown
Collaborator

And maybe specifying:

const maxwidth = 1 << (30 + 10*(^uint(0)>>32&1))

And then changing the buff assignments to:

	buff := (*[maxwidth]byte)(unsafe.Pointer(buff0))

Would be even better.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants