Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions compiler/syscall.go
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,11 @@ func (b *builder) createDarwinFuncPCABI0Call(instr *ssa.CallCommon) llvm.Value {
// in C.
name = "syscall_libc_open"
}
if name == "fcntl" {
// Same for fcntl(), whose third parameter is variadic. See
// src/runtime/os_darwin.c for what goes wrong without the wrapper.
name = "syscall_libc_fcntl"
}
if b.GOARCH == "amd64" {
if name == "fdopendir" || name == "readdir_r" {
// Hack to support amd64, which needs the $INODE64 suffix.
Expand Down
72 changes: 72 additions & 0 deletions src/os/fcntl_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
//go:build (darwin || linux) && !baremetal && !tinygo.wasm && !nintendoswitch

package os_test

import (
. "os"
"syscall"
"testing"
"time"
)

// fcntl takes its third parameter through a variadic list, so the call needs
// the C wrapper in src/runtime/os_darwin.c on darwin.

// F_SETFL must reach fcntl with the value that the caller gave it. A read on
// an empty pipe returns EAGAIN when the descriptor is non-blocking, and blocks
// when the flag did not arrive.
func TestFcntlSetNonblock(t *testing.T) {
var fds [2]int
if err := syscall.Pipe(fds[:]); err != nil {
t.Fatalf("Pipe failed: %v", err)
}
defer syscall.Close(fds[0])
defer syscall.Close(fds[1])

if err := syscall.SetNonblock(fds[0], true); err != nil {
t.Fatalf("SetNonblock failed: %v", err)
}

type result struct {
n int
err error
}
done := make(chan result, 1)
go func() {
buf := make([]byte, 1)
n, err := syscall.Read(fds[0], buf)
done <- result{n, err}
}()

select {
case r := <-done:
if r.err != syscall.EAGAIN {
t.Errorf("wanted EAGAIN from a read on an empty non-blocking pipe, got %d, %v", r.n, r.err)
}
case <-time.After(10 * time.Second):
t.Fatal("the read blocked, so the descriptor is still blocking")
}
}

// The pointer commands go through the same wrapper as the int commands. A
// F_GETLK on a file that nobody locked reports F_UNLCK.
func TestFcntlGetLock(t *testing.T) {
f, err := CreateTemp(t.TempDir(), "fcntl")
if err != nil {
t.Fatalf("CreateTemp failed: %v", err)
}
defer f.Close()

lk := syscall.Flock_t{
Type: syscall.F_RDLCK,
Whence: 0,
Start: 0,
Len: 0,
}
if err := syscall.FcntlFlock(f.Fd(), syscall.F_GETLK, &lk); err != nil {
t.Fatalf("FcntlFlock(F_GETLK) failed: %v", err)
}
if lk.Type != syscall.F_UNLCK {
t.Errorf("wanted F_UNLCK on an unlocked file, got %d", lk.Type)
}
}
13 changes: 13 additions & 0 deletions src/runtime/os_darwin.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// This file is included in the build, despite the //go:build line above.

#include <fcntl.h>
#include <stdint.h>

// Wrapper function because 'open' is a variadic function and variadic functions
// use a different (incompatible) calling convention on darwin/arm64.
Expand All @@ -12,6 +13,18 @@ int syscall_libc_open(const char *pathname, int flags, mode_t mode) {
return open(pathname, flags, mode);
}

// Wrapper function for 'fcntl', whose third parameter is variadic as well.
// A call through a plain three-argument function pointer makes the callee read
// that argument from the stack, which holds an unrelated value.
//
// The argument is a uintptr_t so that the pointer commands reached through
// syscall.fcntlPtr use the same wrapper. Both spellings share
// libc_fcntl_trampoline, and on a little-endian target the int commands read
// the low half of the same stack slot.
int syscall_libc_fcntl(int fd, int cmd, uintptr_t arg) {
return fcntl(fd, cmd, arg);
}

// The following functions are called by the runtime because Go can't call
// function pointers directly.

Expand Down