os: ignore O_TRUNC errors on named pipes and terminal devices on Windows

Prior to Go 1.24, os.OpenFile used to support O_TRUNC on named pipes and
terminal devices, even when the truncation was really ignored. This
behavior was consistent with Unix semantics.

CL 618836 changed the implementation of os.OpenFile on Windows and
unintentionally started returning an error when O_TRUNC was used on such
files.

Fixes #76071

Change-Id: Id10d3d8120ae9aa0548ef05423a172ff4e502ff9
Reviewed-on: https://go-review.googlesource.com/c/go/+/716420
Reviewed-by: Michael Knyszek <mknyszek@google.com>
Reviewed-by: Alex Brainman <alex.brainman@gmail.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Damien Neil <dneil@google.com>
Reviewed-by: Michael Pratt <mpratt@google.com>
This commit is contained in:
qmuntal 2025-10-30 12:36:42 +01:00 committed by Quim Muntal
parent 0e1bd8b5f1
commit 9f3a108ee0
5 changed files with 52 additions and 0 deletions

View file

@ -131,6 +131,14 @@ func Openat(dirfd syscall.Handle, name string, flag uint64, perm uint32) (_ sysc
if flag&syscall.O_TRUNC != 0 { if flag&syscall.O_TRUNC != 0 {
err = syscall.Ftruncate(h, 0) err = syscall.Ftruncate(h, 0)
if err == ERROR_INVALID_PARAMETER {
// ERROR_INVALID_PARAMETER means truncation is not supported on this file handle.
// Unix's O_TRUNC specification says to ignore O_TRUNC on named pipes and terminal devices.
// We do the same here.
if t, err1 := syscall.GetFileType(h); err1 == nil && (t == syscall.FILE_TYPE_PIPE || t == syscall.FILE_TYPE_CHAR) {
err = nil
}
}
if err != nil { if err != nil {
syscall.CloseHandle(h) syscall.CloseHandle(h)
return syscall.InvalidHandle, err return syscall.InvalidHandle, err

View file

@ -2275,3 +2275,16 @@ func TestOpenFileFlagInvalid(t *testing.T) {
} }
f.Close() f.Close()
} }
func TestOpenFileTruncateNamedPipe(t *testing.T) {
t.Parallel()
name := pipeName()
pipe := newBytePipe(t, name, false)
defer pipe.Close()
f, err := os.OpenFile(name, os.O_TRUNC|os.O_RDWR|os.O_CREATE, 0666)
if err != nil {
t.Fatal(err)
}
f.Close()
}

View file

@ -228,3 +228,22 @@ func TestRootSymlinkToDirectory(t *testing.T) {
}) })
} }
} }
func TestRootOpenFileTruncateNamedPipe(t *testing.T) {
t.Parallel()
name := pipeName()
pipe := newBytePipe(t, name, false)
defer pipe.Close()
root, err := os.OpenRoot(filepath.Dir(name))
if err != nil {
t.Fatal(err)
}
defer root.Close()
f, err := root.OpenFile(filepath.Base(name), os.O_TRUNC|os.O_RDWR|os.O_CREATE, 0666)
if err != nil {
t.Fatal(err)
}
f.Close()
}

View file

@ -468,6 +468,14 @@ func Open(name string, flag int, perm uint32) (fd Handle, err error) {
if flag&O_TRUNC == O_TRUNC && if flag&O_TRUNC == O_TRUNC &&
(createmode == OPEN_EXISTING || (createmode == OPEN_ALWAYS && err == ERROR_ALREADY_EXISTS)) { (createmode == OPEN_EXISTING || (createmode == OPEN_ALWAYS && err == ERROR_ALREADY_EXISTS)) {
err = Ftruncate(h, 0) err = Ftruncate(h, 0)
if err == _ERROR_INVALID_PARAMETER {
// ERROR_INVALID_PARAMETER means truncation is not supported on this file handle.
// Unix's O_TRUNC specification says to ignore O_TRUNC on named pipes and terminal devices.
// We do the same here.
if t, err1 := GetFileType(h); err1 == nil && (t == FILE_TYPE_PIPE || t == FILE_TYPE_CHAR) {
err = nil
}
}
if err != nil { if err != nil {
CloseHandle(h) CloseHandle(h)
return InvalidHandle, err return InvalidHandle, err

View file

@ -34,6 +34,10 @@ const (
WSAECONNRESET Errno = 10054 WSAECONNRESET Errno = 10054
) )
const (
_ERROR_INVALID_PARAMETER Errno = 87
)
const ( const (
// Invented values to support what package os expects. // Invented values to support what package os expects.
O_RDONLY = 0x00000 O_RDONLY = 0x00000