Skip to content

fuse: remove the 1GiB buffer view limit#101

Merged
billziss-gh merged 1 commit into
winfsp:masterfrom
chinasasu:fix/large-windows-buffers
May 31, 2026
Merged

fuse: remove the 1GiB buffer view limit#101
billziss-gh merged 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 2 times, most recently 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.

@chinasasu
chinasasu force-pushed the fix/large-windows-buffers branch from 9d910cf to ef379a9 Compare May 31, 2026 07:46
@chinasasu

Copy link
Copy Markdown
Author

Thanks for the review, and for the clear explanation of the maxwidth constant — that makes sense to me.

I agree that raising the fixed buffer view limit with a compile-time width check is a better fit for this codebase: it keeps the existing pattern, stays 32-bit safe, and still fixes the >1 GiB read issue we hit on 64-bit Windows.

I've updated the PR to drop the cByteSlice helper and use:

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

at the affected call sites. The revised patch is pushed to the branch.

Thanks again!

Adopt maintainer suggestion: use a compile-time maxwidth constant
(1 GiB on 32-bit, 1 TiB on 64-bit) instead of the fixed 1 GiB array
view, so large Read/Write requests no longer panic before reaching the
filesystem callback.
@chinasasu
chinasasu force-pushed the fix/large-windows-buffers branch from ef379a9 to ec5fe93 Compare May 31, 2026 07:50
@billziss-gh
billziss-gh merged commit 2fa812d into winfsp:master May 31, 2026
6 checks passed
@billziss-gh

Copy link
Copy Markdown
Collaborator

Thank you. I have merged this in.

@chinasasu
chinasasu deleted the fix/large-windows-buffers branch June 1, 2026 04:50
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