Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

x/sys/unix: ioctl wrapper signatures do not match C on Solaris and AIX #59030

Closed
nshalman opened this issue Mar 14, 2023 · 18 comments
Closed

x/sys/unix: ioctl wrapper signatures do not match C on Solaris and AIX #59030

nshalman opened this issue Mar 14, 2023 · 18 comments
Labels
compiler/runtime Issues related to the Go compiler and/or runtime. FrozenDueToAge NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. OS-AIX OS-illumos OS-Solaris Unfortunate
Milestone

Comments

@nshalman
Copy link

(Can be assigned to me unless some kind soul has the bandwidth to work on it before me.)

Copied from WireGuard/wireguard-go#39 (comment)

It looks like ioctl has the wrong signature in Go. According to https://illumos.org/man/2/ioctl req is an int. In Go, that's int32, not int or uint as the Go code has now. Presumably it's too late to fix that, which is unfortunate.

Is it too late?

Regarding ioctls using negative numbers:

The easiest way to do this is probably just by adding & 0xffffffff onto the end of the constant definition.
(The lazy version, by the way, is just unix.IoctlLifreq(tun.ipfd, unix.SIOCGLIFMTU & 0xffffffff, &l)).

Affected functions:

$ git grep "req uint" unix/syscall_solaris.go
unix/syscall_solaris.go://sys   ioctlRet(fd int, req uint, arg uintptr) (ret int, err error) = libc.ioctl
unix/syscall_solaris.go://sys   ioctlPtrRet(fd int, req uint, arg unsafe.Pointer) (ret int, err error) = libc.ioctl
unix/syscall_solaris.go:func ioctl(fd int, req uint, arg uintptr) (err error) {
unix/syscall_solaris.go:func ioctlPtr(fd int, req uint, arg unsafe.Pointer) (err error) {
unix/syscall_solaris.go:func IoctlSetTermio(fd int, req uint, value *Termio) error {
unix/syscall_solaris.go:func IoctlGetTermio(fd int, req uint) (*Termio, error) {
unix/syscall_solaris.go:func IoctlSetIntRetInt(fd int, req uint, arg int) (int, error) {
unix/syscall_solaris.go:func IoctlSetString(fd int, req uint, val string) error {
unix/syscall_solaris.go:func IoctlLifreq(fd int, req uint, l *Lifreq) error {
unix/syscall_solaris.go:func IoctlSetStrioctlRetInt(fd int, req uint, s *Strioctl) (int, error) {

To anyone labeling this issue, this affects both solaris and illumos.

While I'm likely going to be the one doing the work, I could definitely use some guidance on what approach will make the most sense.

@gopherbot gopherbot added the compiler/runtime Issues related to the Go compiler and/or runtime. label Mar 14, 2023
@gopherbot gopherbot added this to the Unreleased milestone Mar 14, 2023
@bcmills
Copy link
Contributor

bcmills commented Mar 14, 2023

(CC @golang/solaris @golang/illumos)

@cherrymui
Copy link
Member

Do you propose to change the signature of the Ioctl functions on Solaris/Illomos?

cc @ianlancetaylor for whether this type of API change is acceptable.

@cherrymui cherrymui added the NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. label Mar 14, 2023
@ianlancetaylor
Copy link
Contributor

There is no reason for Go function signatures to precisely match C function signatures. It's true that the C function takes a 32-bit integer, but it's fine for the Go function to take a 32- or 64-bit integer depending on the platform. Either way you have to pass a magic constant anyhow. The Go code does the right thing when invoking the system call.

Closing because I don't think there is anything to do. Please comment if you disagree.

@ianlancetaylor ianlancetaylor closed this as not planned Won't fix, can't repro, duplicate, stale Mar 15, 2023
@nshalman
Copy link
Author

From a usability and readability perspective, it seems to me like one would expect to be able to write

err := unix.IoctlLifreq(tun.ipfd, unix.SIOCGLIFMTU, &l)

I'm fairly certain, at least for that ioctl, that my code is the only consumer.
Is it too late to change things to make that possible?

The following feels very clunky (as elaborated here: WireGuard/wireguard-go#39 (comment)) :

reqnum := int(unix.SIOCGLIFMTU)
err := unix.IoctlLifreq(tun.ipfd, uint(reqnum), &l)

I suppose the alternative is to recommend something like:

// some helpful comment explaining why this is necessary
SIOCGLIFMTU := unix.SIOCGLIFMTU & 0xffffffff
// ...
err := unix.IoctlLifreq(tun.ipfd, SIOCGLIFMTU, &l)

But that too feels kind of gross.

I do agree with the reviewer of my other code that the two lines including the extra assignment to do the ioctl looks gross.

If nothing else, it would be nice to clean up the test case with at least a better explanation and ideally a cleaner function call.

@nshalman
Copy link
Author

Sorry, to be explicit, by "the test case" I mean TestLifreqGetMTU
and by the comment I mean (https://github.com/golang/sys/blob/master/unix/syscall_solaris_test.go#L370-L372)

	// SIOCGLIFMTU is negative which confuses the compiler if used inline:
	// Using "unix.IoctlLifreq(ip_fd, unix.SIOCGLIFMTU, &l)" results in
	// "constant -1065850502 overflows uint"

@cherrymui
Copy link
Member

Maybe we could change unix.SIOCGLIFMTU to be a positive number that fits in a uint?

@bcmills
Copy link
Contributor

bcmills commented Mar 15, 2023

Heh. Today I learned that the POSIX ioctl in <stropts.h> doesn't match the Linux ioctl in <sys/ioctl.h>. POSIX specifies the (signed) int signature, whereas Linux defines it as unsigned long.

Actually, https://www.gnu.org/software/gnulib/manual/html_node/ioctl.html links to three different LSB specifications, two of which use int and one of which uses unsigned long. 😵‍💫

The Solaris and Illumos system calls defined in C match the POSIX signature and header file. So I agree that the signatures in x/sys/unix seem to be wrong, but I also agree with @zx2c4 that it's probably too late to fix that. 😞

@nshalman
Copy link
Author

Maybe we could change unix.SIOCGLIFMTU to be a positive number that fits in a uint?

It gets defined in unix/zerrors_solaris_amd64.go which is auto-generated by unix/mkerrors.sh so that might be tricky.

@cherrymui
Copy link
Member

The script can be changed, if we decide to change the constant.

@nshalman
Copy link
Author

Heh. Today I learned that the POSIX ioctl in <stropts.h> doesn't match the Linux ioctl in <sys/ioctl.h>. POSIX specifies the (signed) int signature, whereas Linux defines it as unsigned long.

Thank you for looking into this.

Actually, https://www.gnu.org/software/gnulib/manual/html_node/ioctl.html links to three different LSB specifications, two of which use int and one of which uses unsigned long. 😵‍💫

The Solaris and Illumos system calls defined in C match the POSIX signature and header file. So I agree that the signatures in x/sys/unix seem to be wrong, but I also agree with @zx2c4 that it's probably too late to fix that. 😞

Any suggestions? @cherrymui suggested we could change the way mkerrors.sh defines the constants. Would doing that be backwards compatible with existing code? (My experiments in the go playground suggest not, but I could be doing it wrong...)

@bcmills
Copy link
Contributor

bcmills commented Mar 15, 2023

Neither change would be entirely compatible, unfortunately. I'm not sure whether it's worth a breaking change to fix, but in my opinion if it is worthwhile it would be cleaner to change the signature on Solaris to match the C system call signature than to give the constant a value that isn't exactly equal to the value of the C constant.

@zx2c4
Copy link
Contributor

zx2c4 commented Mar 15, 2023

You mean change req uint into req int? Yea, seems reasonable.

@nshalman
Copy link
Author

Some of these ioctls are very very very likely only in use by my code. It should be fine to do a breaking change on those (in my opinion.)
If anyone else is using them, they will probably appreciate the cleanup that the breaking change allows.

@nshalman
Copy link
Author

For the Lifreq, it could look something like this?

diff --git a/unix/syscall_solaris.go b/unix/syscall_solaris.go
index d3444b6..eb01da4 100644
--- a/unix/syscall_solaris.go
+++ b/unix/syscall_solaris.go
@@ -1120,8 +1120,8 @@ func (l *Lifreq) GetLifruUint() uint {
        return *(*uint)(unsafe.Pointer(&l.Lifru[0]))
 }

-func IoctlLifreq(fd int, req uint, l *Lifreq) error {
-       return ioctlPtr(fd, req, unsafe.Pointer(l))
+func IoctlLifreq(fd int, req int, l *Lifreq) error {
+       return ioctlPtr(fd, uint(req), unsafe.Pointer(l))
 }

 // Strioctl Helpers
diff --git a/unix/syscall_solaris_test.go b/unix/syscall_solaris_test.go
index 6c2b906..dc46bd5 100644
--- a/unix/syscall_solaris_test.go
+++ b/unix/syscall_solaris_test.go
@@ -367,17 +367,13 @@ func TestLifreqGetMTU(t *testing.T) {
        if err != nil {
                t.Fatalf("could not open udp socket: %v", err)
        }
-       // SIOCGLIFMTU is negative which confuses the compiler if used inline:
-       // Using "unix.IoctlLifreq(ip_fd, unix.SIOCGLIFMTU, &l)" results in
-       // "constant -1065850502 overflows uint"
-       reqnum := int(unix.SIOCGLIFMTU)
        var l unix.Lifreq
        for link, mtu := range tc {
                err = l.SetName(link)
                if err != nil {
                        t.Fatalf("Lifreq.SetName(%q) failed: %v", link, err)
                }
-               if err = unix.IoctlLifreq(ip_fd, uint(reqnum), &l); err != nil {
+               if err = unix.IoctlLifreq(ip_fd, unix.SIOCGLIFMTU, &l); err != nil {
                        t.Fatalf("unable to SIOCGLIFMTU: %v", err)
                }
                m := l.GetLifruUint()

I can open a CR for changing the ioctls that I added for wireguard-go. I'm a little more nervous about any other ones that were added for other purposes.

@gopherbot
Copy link

Change https://go.dev/cl/476515 mentions this issue: unix: solaris: match ioctl req argument type to libc type

@bcmills bcmills reopened this Mar 15, 2023
@bcmills
Copy link
Contributor

bcmills commented Mar 15, 2023

Hrm. It appears that this issue also affects aix (CC @golang/aix), and I would expect that we should address it in the same way on both platforms.

Checking various references for ioctl(2), I see

@bcmills bcmills added the OS-AIX label Mar 15, 2023
@bcmills bcmills changed the title x/sys/unix: solaris ioctl wrappers using incorrect function signatures x/sys/unix: ioctl wrapper signatures do not match C on Solaris and AIX Mar 15, 2023
@jclulow
Copy link
Contributor

jclulow commented Mar 15, 2023

I believe it is an SVR4 and STREAMS heritage thing, so I would expect to see it in operating systems from that part of the family tree.

@zx2c4
Copy link
Contributor

zx2c4 commented Mar 15, 2023

Updating the CL to include aix and zos.

@golang golang locked and limited conversation to collaborators Mar 16, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
compiler/runtime Issues related to the Go compiler and/or runtime. FrozenDueToAge NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. OS-AIX OS-illumos OS-Solaris Unfortunate
Projects
None yet
Development

No branches or pull requests

7 participants