From 5d0e101a909b43d099fa0f13216ffc03a8060571 Mon Sep 17 00:00:00 2001 From: Hirador <63920290+Hirador@users.noreply.github.com> Date: Fri, 17 Jul 2026 17:53:46 +0200 Subject: [PATCH 1/2] Fix strict-root mode for files >2GB on 32-bit platforms In strict-root mode files are opened through os.Root, whose internal openat does not set O_LARGEFILE. On 32-bit kernels this makes opening any file larger than 2 GiB fail with EOVERFLOW ("value too large for defined data type"). Since every PS3 ISO exceeds 2 GiB, strict-root mode cannot stream PS3 games on 32-bit hosts (e.g. armv5tel/armv7 NAS units); the relaxed root works only because it uses os.Open, which does add O_LARGEFILE. Wrap *os.Root so its Open/Create forward O_LARGEFILE via os.Root.OpenFile (which passes the flag verbatim to openat), preserving os.Root's symlink-safe traversal. O_LARGEFILE is 0 on 64-bit and non-Linux platforms, so the change is a no-op there. --- cmd/ps3netsrv-go/server.go | 5 ++++- pkg/fs/largefile_linux.go | 11 +++++++++++ pkg/fs/largefile_other.go | 7 +++++++ pkg/fs/strict_root.go | 28 ++++++++++++++++++++++++++++ 4 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 pkg/fs/largefile_linux.go create mode 100644 pkg/fs/largefile_other.go create mode 100644 pkg/fs/strict_root.go diff --git a/cmd/ps3netsrv-go/server.go b/cmd/ps3netsrv-go/server.go index 9194297..25bb52c 100644 --- a/cmd/ps3netsrv-go/server.go +++ b/cmd/ps3netsrv-go/server.go @@ -138,10 +138,13 @@ func (sapp *serverApp) server() error { sysRoot := fs.SystemRoot(fs.NewRelaxedSystemRoot(sapp.Root)) if sapp.StrictRoot { - sysRoot, err = os.OpenRoot(sapp.Root) + root, err := os.OpenRoot(sapp.Root) if err != nil { return fmt.Errorf("open root failed: %w", err) } + // Wrap so large files (>2 GiB, i.e. every PS3 ISO) can be opened on + // 32-bit platforms; os.Root's openat omits O_LARGEFILE. + sysRoot = fs.NewStrictSystemRoot(root) } s := server.Server[handler.State]{ diff --git a/pkg/fs/largefile_linux.go b/pkg/fs/largefile_linux.go new file mode 100644 index 0000000..11df500 --- /dev/null +++ b/pkg/fs/largefile_linux.go @@ -0,0 +1,11 @@ +//go:build linux + +package fs + +import "golang.org/x/sys/unix" + +// openLargeFile is O_LARGEFILE on 32-bit Linux (0x8000 on 386, 0x20000 on arm, +// ...) and 0 on 64-bit. Without it, os.Root's openat rejects files larger than +// 2 GiB on 32-bit kernels with EOVERFLOW ("value too large for defined data +// type"), which breaks streaming of PS3 ISOs on legacy 32-bit NAS hardware. +const openLargeFile = unix.O_LARGEFILE diff --git a/pkg/fs/largefile_other.go b/pkg/fs/largefile_other.go new file mode 100644 index 0000000..6a5be85 --- /dev/null +++ b/pkg/fs/largefile_other.go @@ -0,0 +1,7 @@ +//go:build !linux + +package fs + +// openLargeFile has no effect on non-Linux platforms; large-file access there +// does not depend on an explicit open flag. +const openLargeFile = 0 diff --git a/pkg/fs/strict_root.go b/pkg/fs/strict_root.go new file mode 100644 index 0000000..614eec3 --- /dev/null +++ b/pkg/fs/strict_root.go @@ -0,0 +1,28 @@ +package fs + +import "os" + +// StrictSystemRoot wraps *os.Root to make it usable on 32-bit platforms. +// +// os.Root opens files via a raw openat that does not set O_LARGEFILE, so on a +// 32-bit kernel any file larger than 2 GiB fails to open with EOVERFLOW. Every +// PS3 ISO exceeds that, which makes strict-root mode unusable for PS3 streaming +// on legacy 32-bit NAS hardware. This wrapper forwards O_LARGEFILE through +// os.Root.OpenFile (which passes the flag verbatim to openat) while keeping all +// of os.Root's symlink-safe, path-traversal-safe traversal. +type StrictSystemRoot struct { + *os.Root +} + +// NewStrictSystemRoot wraps an already-opened *os.Root. +func NewStrictSystemRoot(root *os.Root) StrictSystemRoot { + return StrictSystemRoot{Root: root} +} + +func (r StrictSystemRoot) Open(path string) (*os.File, error) { + return r.Root.OpenFile(path, os.O_RDONLY|openLargeFile, 0) +} + +func (r StrictSystemRoot) Create(path string) (*os.File, error) { + return r.Root.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC|openLargeFile, 0666) +} From 5cc6bee803c03a21bc4c189ffe706a1f127f5282 Mon Sep 17 00:00:00 2001 From: Hirador <63920290+Hirador@users.noreply.github.com> Date: Sun, 19 Jul 2026 14:42:11 +0200 Subject: [PATCH 2/2] Move O_LARGEFILE handling into internal/osutil Address review feedback on the strict-root large-file fix: - Move the StrictSystemRoot wrapper and the openLargeFile constant from pkg/fs into internal/osutil, alongside the other os-specific helpers. The constant cannot live in pkg/fs and be referenced from a wrapper there while osutil imports pkg/fs (filetimes) without creating an import cycle, so the wrapper moves too; it only depends on os. - Drop the redundant "//go:build linux" tag. Instead of a filename OS suffix, use an explicit "linux || solaris || aix || zos" constraint, which are exactly the platforms where golang.org/x/sys/unix defines O_LARGEFILE. A plain "unix" constraint would fail to compile on darwin/BSD, where the constant does not exist. cmd/ps3netsrv-go/server.go now calls osutil.NewStrictSystemRoot. --- cmd/ps3netsrv-go/server.go | 2 +- internal/osutil/largefile.go | 14 ++++++++++++++ internal/osutil/largefile_other.go | 8 ++++++++ {pkg/fs => internal/osutil}/strict_root.go | 5 ++++- pkg/fs/largefile_linux.go | 11 ----------- pkg/fs/largefile_other.go | 7 ------- 6 files changed, 27 insertions(+), 20 deletions(-) create mode 100644 internal/osutil/largefile.go create mode 100644 internal/osutil/largefile_other.go rename {pkg/fs => internal/osutil}/strict_root.go (87%) delete mode 100644 pkg/fs/largefile_linux.go delete mode 100644 pkg/fs/largefile_other.go diff --git a/cmd/ps3netsrv-go/server.go b/cmd/ps3netsrv-go/server.go index 25bb52c..f107848 100644 --- a/cmd/ps3netsrv-go/server.go +++ b/cmd/ps3netsrv-go/server.go @@ -144,7 +144,7 @@ func (sapp *serverApp) server() error { } // Wrap so large files (>2 GiB, i.e. every PS3 ISO) can be opened on // 32-bit platforms; os.Root's openat omits O_LARGEFILE. - sysRoot = fs.NewStrictSystemRoot(root) + sysRoot = osutil.NewStrictSystemRoot(root) } s := server.Server[handler.State]{ diff --git a/internal/osutil/largefile.go b/internal/osutil/largefile.go new file mode 100644 index 0000000..a90db70 --- /dev/null +++ b/internal/osutil/largefile.go @@ -0,0 +1,14 @@ +//go:build linux || solaris || aix || zos + +package osutil + +import "golang.org/x/sys/unix" + +// openLargeFile is O_LARGEFILE on 32-bit targets (0x8000 on 386, 0x20000 on +// arm, ...) and 0 on 64-bit ones. Without it, os.Root's openat rejects files +// larger than 2 GiB on a 32-bit kernel with EOVERFLOW ("value too large for +// defined data type"), which breaks streaming of PS3 ISOs on legacy 32-bit NAS +// hardware. The constant is only defined by x/sys/unix on the platforms in the +// build constraint above (Linux, Solaris, AIX, z/OS); see largefile_other.go +// for the rest. +const openLargeFile = unix.O_LARGEFILE diff --git a/internal/osutil/largefile_other.go b/internal/osutil/largefile_other.go new file mode 100644 index 0000000..f64d510 --- /dev/null +++ b/internal/osutil/largefile_other.go @@ -0,0 +1,8 @@ +//go:build !(linux || solaris || aix || zos) + +package osutil + +// openLargeFile has no effect on platforms where x/sys/unix does not define +// O_LARGEFILE (darwin, the BSDs, Windows, ...); large-file access there does +// not depend on an explicit open flag. +const openLargeFile = 0 diff --git a/pkg/fs/strict_root.go b/internal/osutil/strict_root.go similarity index 87% rename from pkg/fs/strict_root.go rename to internal/osutil/strict_root.go index 614eec3..7cc594a 100644 --- a/pkg/fs/strict_root.go +++ b/internal/osutil/strict_root.go @@ -1,4 +1,4 @@ -package fs +package osutil import "os" @@ -10,6 +10,9 @@ import "os" // on legacy 32-bit NAS hardware. This wrapper forwards O_LARGEFILE through // os.Root.OpenFile (which passes the flag verbatim to openat) while keeping all // of os.Root's symlink-safe, path-traversal-safe traversal. +// +// It satisfies pkg/fs.SystemRoot: Open/Create are overridden here, and the +// embedded *os.Root supplies Stat, Remove and Mkdir. type StrictSystemRoot struct { *os.Root } diff --git a/pkg/fs/largefile_linux.go b/pkg/fs/largefile_linux.go deleted file mode 100644 index 11df500..0000000 --- a/pkg/fs/largefile_linux.go +++ /dev/null @@ -1,11 +0,0 @@ -//go:build linux - -package fs - -import "golang.org/x/sys/unix" - -// openLargeFile is O_LARGEFILE on 32-bit Linux (0x8000 on 386, 0x20000 on arm, -// ...) and 0 on 64-bit. Without it, os.Root's openat rejects files larger than -// 2 GiB on 32-bit kernels with EOVERFLOW ("value too large for defined data -// type"), which breaks streaming of PS3 ISOs on legacy 32-bit NAS hardware. -const openLargeFile = unix.O_LARGEFILE diff --git a/pkg/fs/largefile_other.go b/pkg/fs/largefile_other.go deleted file mode 100644 index 6a5be85..0000000 --- a/pkg/fs/largefile_other.go +++ /dev/null @@ -1,7 +0,0 @@ -//go:build !linux - -package fs - -// openLargeFile has no effect on non-Linux platforms; large-file access there -// does not depend on an explicit open flag. -const openLargeFile = 0