cisco / ChezScheme

Chez Scheme
Apache License 2.0
6.91k stars 982 forks source link

FFI problem with ioctl in arm64osx #745

Closed burgerrg closed 8 months ago

burgerrg commented 8 months ago

Summary: The foreign-procedure call to ioctl in arm64osx returns -1 and errno = 14 (Bad address). Under a6osx, it returns 0 as expected. I suspect there is a bug in the way the foreign arguments are passed to ioctl, because the equivalent C code returns 0 as expected on both platforms.

Details:

#include <sys/ioctl.h>
#include <stdio.h>
#include <unistd.h>

int main() {
  printf("(define TIOCGWINSZ #x%lx)\n", TIOCGWINSZ);
  struct winsize buf;
  int rc = ioctl(0, TIOCGWINSZ, &buf);
  printf("ioctl returned %d\n", rc);
  printf("%d rows and %d columns\n", buf.ws_row, buf.ws_col);
  return 0;
}

The C code above shows the number of rows and columns in the tty associated with file descriptor 0. It runs as expected in ARM64 macOS and well as Intel macOS.

The FFI version of this code (see https://stackoverflow.com/questions/77389839/chez-scheme-ffi-procedure-doesnt-work-after-change-to-apple-silicon for the original report) returns -1 from ioctl.

;;; win-size-demo.ss -- Procedure to return the size (in rows and columns)
;;; of the terminal it is running in.
(import (chezscheme))

;; Load the C runtime library. Only tested on macOS. The other clauses
;; were taken from examples in other files of the Chez Scheme
;; source distribution.
(case (machine-type)
  [(i3le ti3le a6le ta6le) (load-shared-object "libc.so.6")]
  [(arm64osx tarm64osx i3osx ti3osx a6osx ta6osx) (load-shared-object "libc.dylib")]
  [(i3nt ti3nt a6nt ta6nt) (begin (load-shared-object "msvcrt.dll")
                                  (load-shared-object "kernel32.dll"))]
  [else (load-shared-object "libc.so")])

;; The preferred way to interrogate the window size is through the
;; `ioctl` function from the underlying C implementation. Here's
;; how that (used to) works.

;; Define some file descriptors for stdin/out. Couldn't find this
;; documented anywhere. These values are from Chez expediter.c.
(define STDIN_FD 0)
(define STDOUT_FD 1)

;; Value from /Library/Developer/CommandLineTools/SDKs/MacOSX13.3.sdk/usr/includes/sys/ttycom.h.
;; This value is caclulated via a C macro in <sys/ioccom.h>.
(define TIOCGWINSZ #x40087468)

;; From <sys/ttycom.h>.
(define-ftype win-size
  (struct
    [ws-row unsigned-short] ; number of rows in the window, in characters
    [ws-col unsigned-short] ; number of columns in the window, in characters
    [ws-xpixel unsigned-short] ; horizontal size of the window, in pixels
    [ws-ypixel unsigned-short] ; vertical size of the window, in pixels
    ))

(define ioctl (foreign-procedure "ioctl" (int int (* win-size)) int))

(define errno (foreign-procedure "(cs)s_errno" () int))
(define strerror (foreign-procedure "(cs)s_strerror" (int) scheme-object))

;; Return the size of the terminal window using the `ioctl` function
;; from the underlying C library.
(define (window-size)
  (let* ((win-size-buf (foreign-alloc (ftype-sizeof win-size)))
         (win-size-ptr (make-ftype-pointer win-size win-size-buf)))
    (let ((the-size (dynamic-wind
                      (lambda () #f)
                      (lambda () (let ((ctl-result (ioctl STDIN_FD TIOCGWINSZ win-size-ptr)))
                                   (if (negative? ctl-result)
                                       (let ((cep (current-error-port)) (err (errno)))
                                         (fprintf cep "Error ~d getting display size: ~a\n"
                                           err (strerror err))
                                         (list -1 -1))
                                       (list (ftype-ref win-size (ws-row) win-size-ptr)
                                             (ftype-ref win-size (ws-col) win-size-ptr)))))
                      (lambda () (foreign-free win-size-buf)))))
      the-size)))
mflatt commented 8 months ago

Is ioctl is a varargs function, so you'd need to use __varargs_after?

burgerrg commented 8 months ago

Yes, that fixed it! Thank you!

weinholt commented 8 months ago

Unrelated, but that case expression for loading the C runtime library is getting out of hand. :)

burgerrg commented 8 months ago
(define ioctl (foreign-procedure (__varargs_after 2) "ioctl" (int int (* win-size)) int))