diff --git a/unix/syscall_linux.go b/unix/syscall_linux.go index e044d5b54..c5a98440e 100644 --- a/unix/syscall_linux.go +++ b/unix/syscall_linux.go @@ -1554,6 +1554,7 @@ func sendmsgN(fd int, iov []Iovec, oob []byte, ptr unsafe.Pointer, salen _Sockle var iova [1]Iovec iova[0].Base = &dummy iova[0].SetLen(1) + iov = iova[:] } } msg.Control = &oob[0] diff --git a/unix/syscall_unix_test.go b/unix/syscall_unix_test.go index afe2dc75f..baff92eea 100644 --- a/unix/syscall_unix_test.go +++ b/unix/syscall_unix_test.go @@ -1015,6 +1015,102 @@ func TestSendmsgBuffers(t *testing.T) { } } +// Issue 56384. +func TestRecvmsgControl(t *testing.T) { + switch runtime.GOOS { + case "solaris", "illumos": + // Test fails on Solaris, saying + // "got 0 control messages, want 1". + // Not sure why; Solaris recvmsg man page says + // "For processes on the same host, recvmsg() can be + // used to receive a file descriptor from another + // process, but it cannot receive ancillary data." + t.Skipf("skipping on %s", runtime.GOOS) + } + + fds, err := unix.Socketpair(unix.AF_UNIX, unix.SOCK_STREAM, 0) + if err != nil { + t.Fatal(err) + } + defer unix.Close(fds[0]) + defer unix.Close(fds[1]) + + const payload = "hello" + + // Start a goroutine that sends a control message followed by + // a payload on fds[1]. + go func() { + f, err := os.Create(filepath.Join(t.TempDir(), "file")) + if err != nil { + t.Error(err) + return + } + defer f.Close() + + rc, err := f.SyscallConn() + if err != nil { + t.Error(err) + return + } + var rights []byte + err = rc.Control(func(fd uintptr) { + rights = unix.UnixRights(int(fd)) + }) + if err != nil { + t.Error(err) + return + } + + _, err = unix.SendmsgN(fds[1], nil, rights, nil, 0) + if err != nil { + t.Error(err) + } + if _, err := unix.Write(fds[1], []byte(payload)); err != nil { + t.Error(err) + } + }() + + // Read the control message sent by the goroutine. The + // goroutine writes to fds[1], we read from fds[0]. + + cbuf := make([]byte, unix.CmsgSpace(4)) + _, cn, _, _, err := unix.Recvmsg(fds[0], nil, cbuf, 0) + if err != nil { + t.Fatal(err) + } + cbuf = cbuf[:cn] + + // Read the payload sent by the goroutine. + + buf := make([]byte, len(payload)) + n, err := unix.Read(fds[0], buf) + if err != nil { + t.Fatal(err) + } + buf = buf[:n] + if payload != string(buf) { + t.Errorf("read payload %q, want %q", buf, payload) + } + + // Check the control message. + + cmsgs, err := unix.ParseSocketControlMessage(cbuf) + if err != nil { + t.Fatal(err) + } + if len(cmsgs) != 1 { + t.Fatalf("got %d control messages, want 1", len(cmsgs)) + } + cfds, err := unix.ParseUnixRights(&cmsgs[0]) + if err != nil { + t.Fatal(err) + } + if len(cfds) != 1 { + t.Fatalf("got %d fds, want 1", len(cfds)) + } + defer unix.Close(cfds[0]) +} + // mktmpfifo creates a temporary FIFO and provides a cleanup function. func mktmpfifo(t *testing.T) (*os.File, func()) { err := unix.Mkfifo("fifo", 0666)