Go's cgo system for calling C functions offers a very convenient feature. As the documentation puts it:
Any C function (even void functions) may be called in a multiple assignment context to retrieve both the return value (if any) and the C errno variable as an error [...]
Reading this, you may be tempted to write more or less standard Go error-handling code like the following:
kcid, err := C.kstat_chain_update(t.kc)
if err != nil {
return err
}
This code is a potential mistake. Unless the documentation for the C function you're calling says so explicitly, there is no guarantee that
errno
is zero on success . If the function returns success but
errno
is non-zero, cgo will dutifully generate a non-nil error return from it and then your Go code will bail out with an error that isn't.
This is not cgo's fault. Cgo has no magic knowledge of what C function return values are and aren't errors, so all it can do is exactly what it said it was going to do; if
errno
is non zero, you get an error version of it. This is just a C API issue (that ultimately comes about because
errno
is both an implicit return and global state). You'd never write code like this in Go, where 'only return non-nil error on actual errors' is well established, but we're stuck with the C API that we actually have instead of the Go-like one we'd like. So we have to deal with it, which means checking return values explicitly.
(In this case the real 'there has been an error' marker is a
kcid
return value of -1. I actually hit an irregular test failure when my code was just checking
err
, which is how I re-stubbed my toe on this particular C API issue.)
PS: the ultimate cause of this is that C code often doesn't explicitly set
errno
to zero on success but instead leaves it alone, which means
errno
can wind up set from whatever internal system call or library routine last failed and set it. There are many possibilities for how this can happen; a classical one is seeing
ENOTTY
from something checking to see if the file descriptor it is writing to is a TTY and so should be in line-buffered mode.
(In my case I saw
EAGAIN
, which I believe was the OmniOS kernel telling
kstat_chain_update()
that the buffer it had been given wasn't large enough, please try again with a bigger one.)