diff --git a/unix/syscall_linux.go b/unix/syscall_linux.go index c5a98440e..73b12b680 100644 --- a/unix/syscall_linux.go +++ b/unix/syscall_linux.go @@ -1986,12 +1986,10 @@ func bytes2iovec(bs [][]byte) []Iovec { return iovecs } -// offs2lohi splits offs into its lower and upper unsigned long. On 64-bit -// systems, hi will always be 0. On 32-bit systems, offs will be split in half. -// preadv/pwritev chose this calling convention so they don't need to add a -// padding-register for alignment on ARM. +// offs2lohi splits offs into its low and high order bits. func offs2lohi(offs int64) (lo, hi uintptr) { - return uintptr(offs), uintptr(uint64(offs) >> SizeofLong) + const longBits = SizeofLong * 8 + return uintptr(offs), uintptr(uint64(offs) >> longBits) } func Readv(fd int, iovs [][]byte) (n int, err error) { diff --git a/unix/syscall_linux_test.go b/unix/syscall_linux_test.go index 9fc641ab7..170e37ee9 100644 --- a/unix/syscall_linux_test.go +++ b/unix/syscall_linux_test.go @@ -1099,3 +1099,36 @@ func TestIoctlFileDedupeRange(t *testing.T) { dedupe.Info[1].Bytes_deduped, 0) } } + +// TestPwritevOffsets tests golang.org/issues/57291 where +// offs2lohi was shifting by the size of long in bytes, not bits. +func TestPwritevOffsets(t *testing.T) { + path := filepath.Join(t.TempDir(), "x.txt") + + f, err := os.Create(path) + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { f.Close() }) + + const ( + off = 20 + ) + b := [][]byte{{byte(0)}} + n, err := unix.Pwritev(int(f.Fd()), b, off) + if err != nil { + t.Fatal(err) + } + if n != len(b) { + t.Fatalf("expected to write %d, wrote %d", len(b), n) + } + + info, err := f.Stat() + if err != nil { + t.Fatal(err) + } + want := off + int64(len(b)) + if info.Size() != want { + t.Fatalf("expected size to be %d, got %d", want, info.Size()) + } +}