;;; Copyright (c) 1993 by Olin Shivers.
;;; Signal handler system

;;; The principal trickiness here is that we have to interface to Unix signals
;;; *through* an intermediate interface, the S48 vm's idea of interrupts.
;;; So there is a difference between delivering a signal to the underlying
;;; Unix process and delivering it to the program that runs on the VM.
;;;
;;; One effect is that we have two separate codes for the same thing -- the
;;; Unix signal code, and the S48 interrupt value. E.g., SIGNAL/TSTP and
;;; INTERRUPT/TSTP.

;;; These system calls can return EINTR or restart. In order for the S48 vm's
;;; interrupt system to detect a signal and invoke the handler, they *must*
;;; return EINTR, and this must cause a return from C to Scheme.
;;;
;;; open close dup2 accept connect
;;; read recv recvfrom recvmsg
;;; write send sendto sendmsg
;;; select
;;; wait
;;; fcntl* ioctl
;;; sigsuspend
;;; HP-UX, but I don't use: poll lockf msem_lock msgsnd msgrcv semop
;;; 
;;; * Only during a F_SETLKW
;;;
;;; From rts/interrupt.scm (package interrupts, interface interrupts-interface)
;;;     WITH-INTERRUPTS INTERRUPT-HANDLERS SET-ENABLED-INTERRUPTS !
;;;	ENABLED-INTERRUPTS
;;; Must define WITH-INTERRUPTS* and WITH-INTERRUPTS.

;;; Map a Unix async signal to its S48 interrupt value.
;;; -1 => Not defined.
(import-lambda-definition %signal->interrupt (sig) "sig2interrupt")

(define (signal->interrupt sig)
  (let ((int (%signal->interrupt sig)))
    (if (>= int 0) int
	(error "Unix signal has no Scheme 48 interrupt." sig))))


(define (interrupt-enabled? int mask)
  (interrupt-in-set? int mask))

(define (interrupt-enable int mask)
  (insert-interrupt int mask))

(define *enabled-interrupts* 
   (let lp ((i 0) (mask 0))
     (if (= i number-of-interrupts)
	 mask
	 (lp (+ i 1) (interrupt-enable i mask)))))

(define (enabled-interrupts) *enabled-interrupts*)

(define *pending-interrupts* 0)

(define (interrupt-pending? int)
  (interrupt-in-set? int *pending-interrupts*))

(define (make-interrupt-pending int)
  (insert-interrupt int *pending-interrupts*))

(define (remove-pending-interrupt int)
  (remove-interrupt int *pending-interrupts*))

;;; I'm trying to be consistent about the ! suffix -- I don't use it
;;; when frobbing process state. This is not a great rule; perhaps I
;;; should change it.
;;; 
;;; I think you should...
(define (set-enabled-interrupts new-enabled-interrupts)
  (do ((int 0 (+ int 1)))
      ((= int number-of-interrupts) new-enabled-interrupts)
    (let ((old-state (interrupt-enabled? int *enabled-interrupts*))
	  (new-state (interrupt-enabled? int new-enabled-interrupts)))
      (if (and (not old-state) new-state (interrupt-pending? int))
	  (call-interrupt-handler int))))
  (set! *enabled-interrupts* new-enabled-interrupts))

(define-simple-syntax (with-enabled-interrupts interrupt-set body ...)
   (begin 
     (with-enabled-interrupts* interrupt-set (lambda () body ...))))

(define (with-enabled-interrupts* interrupt-set thunk)
  (let ((before *enabled-interrupts*))
    (set-enabled-interrupts interrupt-set)
    (let ((return (thunk)))
      (set-enabled-interrupts before)
      return)))


(define *interrupt-handlers-vector* 
  (make-vector number-of-interrupts #t))

(define (interrupt-handlers-vector)
  *interrupt-handlers-vector*)

(define (interrupt-handler-ref int)
  (if (or (< int 0) (>= int number-of-interrupts))
      (error "ill signum in interrupt-handler-ref" int)
      (vector-ref *interrupt-handlers-vector* int)))

(define (call-interrupt-handler int)
  (let ((handler (interrupt-handler-ref int)))
    (case handler
      ((#t) ((vector-ref default-int-handler-vec int) (enabled-interrupts)))
      ((#f) (if #f #f))
      (else (handler (enabled-interrupts))))))
    

;;; Get/Set signal handlers
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;;; When you set a signal's handler to "default," if the default for that
;;; signal is something other than "ignore," we actually install this guy.
;;; When he is called by the S48 interrupt system, he'll magically make
;;; the default action happen (by calling C code that *really* sets the
;;; handler to SIGDFL, and then re-sending the signal). This basically
;;; terminates the process, since if the default isn't "ignore," it's always
;;; "terminate" of some kind. Doing it this way means the exit code given
;;; to our waiting parent proc correctly reflects how we died, and also
;;; makes the core dump happen if it should. Details, details.

(import-lambda-definition %do-default-sigaction (signal) "do_default_sigaction")

(define default-int-handler-vec
  ;; Non-Unix-signal interrupts just get their default values from
  ;; the current value of I-H.
  (let ((v (make-vector 32)))
    (do ((sig 31 (- sig 1)))			; For each Unix signal
	((< sig 0))				; make & install a default
      (let ((i (%signal->interrupt sig)))	; signal handler.
	(if (>= i 0)	; Don't mess with non-signal interrupts.
	    (vector-set! v i (if (memv sig signals-ignored-by-default)
				 (lambda (enabled-interrupts) #f)
				 (lambda (enabled-interrupts)
				   (%do-default-sigaction sig)))))))
    v))


;;; HANDLER is #f (ignore), #t (default), or a procedure taking an integer
;;; argument. The interrupt is delivered to a procedure by (1) setting the
;;; ENABLED-INTERRUPTS register to 0 (i.e., blocking all interrupts), and (2)
;;; applying the procedure to the previous value of the ENABLED-INTERRUPTS
;;; register. If the procedure returns normally, the ENABLED-INTERRUPTS 
;;; register will be restored to its previous value.

(define (set-interrupt-handler int handler)
  (if (or (< int 0) (>= int number-of-interrupts))
      (error "ill signum in set-interrupt-handler!" int)
      (let ((old-handler (vector-ref *interrupt-handlers-vector* int)))
	(vector-set! *interrupt-handlers-vector* int handler)
	old-handler)))

(define (interrupt-handler int)
  (interrupt-handler-ref int))

(define (with-scsh-sighandlers interactive? thunk)
  (do ((int 0 (+ int 1)))
      ((= int number-of-interrupts))
    (set-interrupt-handler
     int
     (lambda a #f)))
  (do ((sig 32 (- sig 1)))
      ((< sig 0))
    (let ((i (%signal->interrupt sig)))
      (if (not (or (= i -1)
		   (= sig signal/alrm)))	; Leave alarm handler alone.
	  (set-interrupt-handler 
	   i
	   #t))))
  (let ((scsh-initial-thread  (current-thread)))
    (if (not (eq? (thread-name scsh-initial-thread)
		  'scsh-initial-thread))
	(error "sighandler did not find scsh-initial-thread, but"
	       scsh-initial-thread))
    
    ;; Note: this will prevent any other system to work, since it pushes
    ;; a new command level !
    (if interactive?
	(set-interrupt-handler interrupt/keyboard
			       (lambda stuff
				 ((structure-ref threads-internal schedule-event) 
				  scsh-initial-thread
				  (enum  
				   (structure-ref threads-internal event-type) 
				   interrupt)
				  (enum interrupt keyboard))))))
  (run-as-long-as deliver-interrupts thunk 'deliver-interrupts))

(define (deliver-interrupts)
  (let lp ((last ((structure-ref sigevents most-recent-sigevent))))
    (let ((event ((structure-ref sigevents next-sigevent-set)
		  last full-interrupt-set)))
      (call-interrupt-handler ((structure-ref sigevents sigevent-type) event))
      (lp event))))
    
;;; I am ashamed to say the 33 below is completely bogus.
;;; What we want is a value that is 1 + max interrupt value.

(define int->sig-vec
  (let ((v (make-vector 33 #f)))
    (do ((sig 32 (- sig 1)))
	((< sig 0))
      (let ((i (%signal->interrupt sig)))
	(if (not (= i -1)) (vector-set! v i sig))))
    v))

(define (int->signal i) (and (<= 0 i 32) (vector-ref int->sig-vec i)))
