;;; select(2) syscall for scsh. -*- Scheme -*-
;;; Copyright (c) 1995 by Olin Shivers.

(foreign-init-name "select")


(foreign-source
  "/* Make sure foreign-function stubs interface to the C funs correctly: */"
  "#include \"select1.h\""
  "" "")

;;; TIMEOUT is 0 for immediate, >0 for timeout, #f for infinite; 
;;;     default is #f.
;;; The sets are vectors of file descriptors & fd ports.
;;; You get three new vectors back.

; The following routines copy ports to fd's, and copy fd's back to fd's and
; ports, so that select can take numbers and ports, simultaneously.

; This is a C procedure in scheme.  So sue me.  At least it's tail-recursive
(define (fd-filter filter-me)
  (let* ((len (vector-length filter-me))
	 (vector-to-return (make-vector len)))
    (let loop ((count (- len 1)))
      (if (>= count 0)
	  (let ((ref (vector-ref filter-me count)))
	    (if (integer? ref)
		(vector-set! vector-to-return count ref)
		(vector-set! vector-to-return count (port->fdes ref)))
	    (loop (- count 1)))))
    vector-to-return))

; ! means side-effect, the next one is more functional.
(define (fd-copyback! orig form)
  (let loop ((count (- (vector-length orig) 1)))
    (if (>= count 0)
	(begin
	  (if (not (vector-ref form count))
	      (vector-set! orig count #f)
	      (vector-set! form count (vector-ref orig count)))
	  (loop (- count 1)))))
  orig)

(define (fd-copyback orig form)
  (let* ((len (vector-length orig))
	 (vector-to-return (make-vector len #f)))
    (let loop ((count (- len 1)))
      (if (>= count 0)
	  (begin
	    (if (vector-ref form count)
		(vector-set! vector-to-return count (vector-ref orig count)))
	    (loop (- count 1)))))
    vector-to-return))
    
(define (select read-vec write-vec exception-vec . maybe-timeout)
  (let ((rv (copy-vector read-vec))
	(wv (copy-vector write-vec))
	(ev (copy-vector exception-vec)))
    (receive (nr nw ne) (apply select! rv wv ev maybe-timeout)
      (values (vector-take-form read-vec rv nr)
	      (vector-take-form write-vec wv nw)
	      (vector-take-form exception-vec ev ne)))))


(define (select!/copyback read-vec write-vec exception-vec . maybe-timeout)
  (receive (errno nr nw ne)
           (apply select!/copyback/errno read-vec write-vec exception-vec
		                         maybe-timeout)
     (if errno
	 (apply errno-error errno select!/copyback
		read-vec write-vec exception-vec maybe-timeout)
	 (values nr nw ne))))


(define (select!/copyback/errno read-vec write-vec
				exception-vec . maybe-timeout)
  (let ((timeout (and (pair? maybe-timeout)
		      (if (pair? (cdr maybe-timeout))
			  (apply error "Too many arguments"
				 select!/copyback/errno
				 read-vec write-vec exception-vec
				 maybe-timeout)
			  (real->exact-integer (check-arg real?
							  (car maybe-timeout)
							  select!/copyback/errno)))))
		     
	(vec-ok? (lambda (v)
		   (vector-every? (lambda (elt)
				    (or (and (integer? elt) (>= elt 0))
					(fdport? elt)))
				  v))))
    ;; Type-check input vectors.
    (check-arg vec-ok?      read-vec select!/copyback/errno)
    (check-arg vec-ok?     write-vec select!/copyback/errno)
    (check-arg vec-ok? exception-vec select!/copyback/errno)
    (check-arg (lambda (x) (or (not x) (integer? x))) timeout
	       select!/copyback/errno)

    (let ((prop-read-vec (fd-filter read-vec))
	  (prop-write-vec (fd-filter write-vec))
	  (prop-exception-vec (fd-filter exception-vec)))
      (let lp ()
	(receive (errno nr nw ne)
		 (%select/copyback/errno prop-read-vec prop-write-vec prop-exception-vec timeout)
  	  (if (and errno (= errno errno/intr))	; Retry on interrupts.
	      (lp)
	      (values errno 
		      (fd-copyback read-vec nr)
		      (fd-copyback write-vec nw)
		      (fd-copyback exception-vec ne))))))))


(define-foreign %select/copyback/errno
  (select_copyback (vector-desc rvec)
		   (vector-desc wvec)
		   (vector-desc evec)
		   (desc nsecs))	; Integer or #f for infinity.
  desc		; errno or #f
  fixnum	; nread   - number of hits in RVEC
  fixnum	; nwrite  - number of hits in WVEC
  fixnum)	; nexcept - number of hits in EVEC


(define (vector-take-form vec form nelts)
  (let ((short (make-vector nelts)))
    (do ((i (- (vector-length vec) 1) (- i 1)))
	((< i 0))
      (if (vector-ref form i)
	  (begin
	    (set! nelts (- nelts 1))
	    (vector-set! short nelts (vector-ref vec i)))))
    short))


;;; SELECT!
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; The side-effecting variant. To be documented.

(define (select! read-vec write-vec exception-vec . maybe-timeout)
  (receive (errno nr nw ne)
           (apply select!/errno read-vec write-vec exception-vec maybe-timeout)
     (if errno
	 (apply errno-error errno select! read-vec write-vec exception-vec
		maybe-timeout)
	 (values nr nw ne))))

(define (select!/errno read-vec write-vec exception-vec . maybe-timeout)
  (let ((timeout (and (pair? maybe-timeout)
		      (if (pair? (cdr maybe-timeout))
			  (apply error "Too many arguments"
				 select!/copyback/errno
				 read-vec write-vec exception-vec
				 maybe-timeout)
			  (real->exact-integer (check-arg real?
							  (car maybe-timeout)
							  select!/copyback/errno)))))
		     
	(vec-ok? (lambda (v)
		   (vector-every? (lambda (elt)
				    (or (and (integer? elt) (>= elt 0))
					(not elt)
					(fdport? elt)))
				  v))))
    ;; Type-check input vectors.
    (check-arg vec-ok?      read-vec select!/errno)
    (check-arg vec-ok?     write-vec select!/errno)
    (check-arg vec-ok? exception-vec select!/errno)
    (check-arg (lambda (x) (or (not x) (integer? x))) timeout select!/errno)
	
    (let ((prop-read-vec (fd-filter read-vec))
	  (prop-write-vec (fd-filter write-vec))
	  (prop-exception-vec (fd-filter exception-vec)))
      (let lp ()
	(receive (errno nr nw ne)
		 (%select!/errno prop-read-vec prop-write-vec prop-exception-vec timeout)
  	  (if (and errno (= errno errno/intr))	; Retry on interrupts.
	      (lp)
	      (begin
		(fd-copyback! read-vec prop-read-vec)
		(fd-copyback! write-vec prop-write-vec)
		(fd-copyback! exception-vec prop-exception-vec)
		(values errno nr nw ne))))))))


(define-foreign %select!/errno
  (select_filter (vector-desc rvec)
		 (vector-desc wvec)
		 (vector-desc evec)
		 (desc nsecs))		; Integer or #f for infinity.
  desc		; errno or #f
  fixnum	; nread   - number of hits in RVEC
  fixnum	; nwrite  - number of hits in WVEC
  fixnum)	; nexcept - number of hits in EVEC
