1046 lines
34 KiB
Scheme
1046 lines
34 KiB
Scheme
;;; A Unix file port system to completely replace S48 file ports.
|
|
;;; We use S48 extensible ports.
|
|
;;; Copyright (c) 1993 by Olin Shivers.
|
|
|
|
(define-record fdport-data
|
|
channel
|
|
revealed)
|
|
|
|
(define-record-discloser type/fdport-data
|
|
(lambda (r)
|
|
(list 'fdport-data
|
|
(fdport-data:channel r) (fdport-data:revealed r))))
|
|
|
|
; This stuff is _weak_.
|
|
; Vector of weak pointers mapping fd -> fdport.
|
|
|
|
(define fdports (make-integer-table))
|
|
|
|
(define (install-fdport fdport)
|
|
(let* ((fdport* (fdport-data fdport))
|
|
(ch (fdport-data:channel fdport*))
|
|
(ch-number (channel-os-index ch)))
|
|
(if (not (= (fdport-data:revealed fdport*) 0))
|
|
(table-set! fdports ch-number fdport)
|
|
(begin
|
|
(weak-table-set! fdports ch-number fdport)
|
|
(%set-cloexec (fdport-data:fd (port-data fdport)) #t)))))
|
|
|
|
(define (maybe-fdes->port fdes)
|
|
(weak-table-ref fdports fdes))
|
|
|
|
;Hmm... these shouldn't be necessary. But still.
|
|
;Fake defrec routines for backwards compatibility.
|
|
(define (fdport-data:fd fdport*)
|
|
(channel-os-index (fdport-data:channel fdport*)))
|
|
|
|
(define (fdport-data:closed? fdport*)
|
|
(eq? (channel-status (fdport-data:channel fdport*))
|
|
(enum channel-status-option closed)))
|
|
|
|
;;; Support for channel-ready?
|
|
;;; This applies to input- and output-ports
|
|
|
|
(define (fdport-channel-ready? fdport)
|
|
(channel-ready? (fdport-data:channel (port-data fdport))))
|
|
|
|
;Arbitrary, for now.
|
|
(define buffer-size 255)
|
|
|
|
(define open-fdchannel open-channel)
|
|
|
|
(define (make-input-fdchannel fd)
|
|
(open-fdchannel fd (enum channel-status-option input)))
|
|
|
|
(define (make-output-fdchannel fd)
|
|
(open-fdchannel fd (enum channel-status-option output)))
|
|
|
|
;The two following routines are to build ports from stdin and stdout channels.
|
|
(define (channel-port->input-fdport channel-port)
|
|
(let ((p (make-buffered-input-port input-fdport-handler
|
|
(make-fdport-data
|
|
(channel-cell-ref (port-data channel-port)) 1)
|
|
(make-byte-vector buffer-size 0) 0 0)))
|
|
(obtain-port-lock channel-port)
|
|
(set-port-lock! p (port-lock channel-port))
|
|
(set-port-locked?! p (port-locked? channel-port))
|
|
(install-fdport p)
|
|
(release-port-lock channel-port)
|
|
p))
|
|
|
|
(define (channel-port->output-fdport channel-port)
|
|
(let ((p (make-buffered-output-port
|
|
output-fdport-handler
|
|
(make-fdport-data (channel-cell-ref(port-data channel-port)) 1)
|
|
(make-byte-vector buffer-size 0) 0 buffer-size)))
|
|
(obtain-port-lock channel-port)
|
|
(set-port-lock! p (port-lock channel-port))
|
|
(set-port-locked?! p (port-locked? channel-port))
|
|
(install-fdport p)
|
|
(periodically-force-output! p)
|
|
(release-port-lock channel-port)
|
|
p))
|
|
|
|
(define (channel-port->unbuffered-output-fdport channel-port)
|
|
(let ((p (make-unbuffered-output-port unbuffered-output-fdport-handler
|
|
(make-fdport-data
|
|
(channel-cell-ref (port-data channel-port)) 1))))
|
|
(obtain-port-lock channel-port)
|
|
(set-port-lock! p (port-lock channel-port))
|
|
(set-port-locked?! p (port-locked? channel-port))
|
|
(install-fdport p)
|
|
(periodically-force-output! p)
|
|
(release-port-lock channel-port)
|
|
p))
|
|
|
|
(define (alloc-input-fdport fd revealed)
|
|
(make-buffered-input-port input-fdport-handler
|
|
(make-fdport-data (make-input-fdchannel fd) revealed)
|
|
(make-byte-vector buffer-size 0) 0 0))
|
|
|
|
(define (alloc-output-fdport fd revealed)
|
|
(make-buffered-output-port output-fdport-handler
|
|
(make-fdport-data (make-output-fdchannel fd) revealed)
|
|
(make-byte-vector buffer-size 0) 0 buffer-size))
|
|
|
|
(define (make-input-fdport fd revealed)
|
|
(let ((p (alloc-input-fdport fd revealed)))
|
|
(install-fdport p)
|
|
p))
|
|
|
|
(define (make-output-fdport fd revealed)
|
|
(let ((p (alloc-output-fdport fd revealed)))
|
|
(periodically-force-output! p)
|
|
(install-fdport p)
|
|
p))
|
|
|
|
(define (fdport? x)
|
|
(cond ((or (and (input-port? x) (port-data x))
|
|
(and (output-port? x) (port-data x)))
|
|
=> (lambda (d) (fdport-data? d)))
|
|
(else #f)))
|
|
|
|
(define fdport-null-method (lambda (x) x #f))
|
|
|
|
(define null-func (lambda args #t))
|
|
|
|
(define (close-fdport* fdport*)
|
|
(table-set! fdports (channel-os-index (fdport-data:channel fdport*)) #f)
|
|
(close-channel (fdport-data:channel fdport*)))
|
|
|
|
;The handlers drop straight through to the convenient channel routines.
|
|
(define (make-input-fdport-handler bufferproc)
|
|
(make-buffered-input-port-handler
|
|
(lambda (fdport*)
|
|
(list 'input-fdport (fdport-data:channel fdport*)))
|
|
close-fdport*
|
|
bufferproc
|
|
fdport-channel-ready?
|
|
(lambda (fdport* owner)
|
|
(steal-channel! (fdport-data:channel fdport*) owner))))
|
|
|
|
(define input-fdport-handler
|
|
(make-input-fdport-handler
|
|
(lambda (fdport* buffer start needed)
|
|
(channel-read buffer start needed (fdport-data:channel fdport*)))))
|
|
|
|
(define (make-output-fdport-handler bufferproc)
|
|
(make-buffered-output-port-handler
|
|
(lambda (fdport*)
|
|
(list 'output-fdport (fdport-data:channel fdport*)))
|
|
close-fdport*
|
|
bufferproc
|
|
fdport-channel-ready?
|
|
(lambda (fdport* owner)
|
|
(steal-channel! (fdport-data:channel fdport*) owner))))
|
|
|
|
(define output-fdport-handler
|
|
(make-output-fdport-handler
|
|
(lambda (fdport* buffer start count)
|
|
(channel-write buffer start count (fdport-data:channel fdport*)))))
|
|
|
|
(define unbuffered-output-fdport-handler
|
|
(let ((buffer (make-byte-vector 1 0)))
|
|
(make-output-fdport-handler
|
|
(lambda (fdport* char)
|
|
(byte-vector-set! buffer 0 (char->ascii char))
|
|
(channel-write buffer 0 1 (fdport-data:channel fdport*))))))
|
|
|
|
(define fdport-data port-data)
|
|
; That was easy.
|
|
|
|
(define (guess-output-policy port)
|
|
(if (= 0 (port-limit port))
|
|
bufpol/none
|
|
bufpol/block))
|
|
|
|
(define (set-port-buffering port policy . maybe-size)
|
|
(cond ((and (fdport? port) (open-input-port? port))
|
|
(let ((size (if (pair? maybe-size) (car maybe-size)
|
|
(if (= policy bufpol/none) 1 255))))
|
|
(if (<= size 0)
|
|
(error "buffer size must be at least 1 for input ports"
|
|
port policy size))
|
|
(set-input-port-buffering port policy size)))
|
|
((and (fdport? port) (open-output-port? port))
|
|
(let ((size (if (pair? maybe-size) (car maybe-size)
|
|
(if (= policy bufpol/none) 0 255))))
|
|
(if (< size 0)
|
|
(error "buffer size must be at least 0 for output ports"
|
|
port policy size))
|
|
(set-output-port-buffering port policy size)))
|
|
(else
|
|
(error "Not a port" port))))
|
|
|
|
(define (set-output-port-buffering port policy size)
|
|
(cond ((eq? policy bufpol/none)
|
|
(if (not (= size 0))
|
|
(error "buffer size must be 0 for bufpol/none on output ports"
|
|
port policy size))
|
|
(install-nullbuffer port unbuffered-output-fdport-handler))
|
|
((eq? policy bufpol/block)
|
|
(if (= size 0)
|
|
(install-nullbuffer port unbuffered-output-fdport-handler)
|
|
(let ((old-size (byte-vector-length (port-buffer port)))
|
|
(new-buffer (make-byte-vector size 0)))
|
|
(if (< size old-size)
|
|
(begin
|
|
(really-force-output port)
|
|
(obtain-port-lock port)
|
|
(set-port-index! port 0))
|
|
(begin
|
|
(obtain-port-lock port)
|
|
(copy-bytes! (port-buffer port) 0 new-buffer 0 old-size)))
|
|
(install-buffer port new-buffer size)
|
|
(release-port-lock port))))
|
|
((eq? policy bufpol/line)
|
|
;(install-nullbuffer port (make-line-output-proc size)))
|
|
(error "bufpol/line is currently not supported"))
|
|
(else (error "policy not supported " policy))))
|
|
|
|
(define (install-nullbuffer port handler)
|
|
(really-force-output port)
|
|
(obtain-port-lock port)
|
|
(set-port-limit! port 0)
|
|
(set-port-index! port 0)
|
|
(set-port-buffer! port (make-byte-vector 0 0))
|
|
(set-port-handler! port handler)
|
|
(release-port-lock port))
|
|
|
|
(define (install-buffer port new-buffer size)
|
|
(if (eq? bufpol/none (guess-output-policy port))
|
|
(set-port-handler! port output-fdport-handler))
|
|
(set-port-limit! port size)
|
|
(set-port-buffer! port new-buffer))
|
|
|
|
; TODO flush on stdinput is required but probably impossible since current-input-port is a fluid and may change without notice. One possibility would be to override (current-input-port)
|
|
|
|
;;; This port can ONLY be flushed with a newline or a close-output
|
|
;;; flush-output won't help
|
|
(define (make-line-output-proc size)
|
|
(let ((proc-buffer (make-byte-vector size 0))
|
|
(proc-buffer-index 0))
|
|
(make-buffered-output-port-handler
|
|
(lambda (fdport*)
|
|
(list 'output-fdport (fdport-data:channel fdport*)))
|
|
(lambda (fdport*)
|
|
(channel-write proc-buffer
|
|
0
|
|
proc-buffer-index
|
|
(fdport-data:channel fdport*))
|
|
(close-fdport* fdport*))
|
|
(lambda (fdport* char)
|
|
(byte-vector-set! proc-buffer proc-buffer-index (char->ascii char))
|
|
(set! proc-buffer-index (+ proc-buffer-index 1))
|
|
(cond ((or (eq? char #\newline) (= proc-buffer-index size))
|
|
(channel-write proc-buffer
|
|
0
|
|
proc-buffer-index
|
|
(fdport-data:channel fdport*))
|
|
(set! proc-buffer-index 0))))
|
|
fdport-channel-ready?
|
|
(lambda (fdport* owner)
|
|
(steal-channel! (fdport-data:channel fdport*) owner)))))
|
|
|
|
|
|
(define (set-input-port-buffering port policy size)
|
|
(cond ((eq? policy bufpol/none)
|
|
(if (not (= size 1))
|
|
(error "buffer size must be 1 for bufpol/none on input ports"
|
|
port policy size))
|
|
(set-input-port-buffering port bufpol/block 1))
|
|
((eq? policy bufpol/block)
|
|
(install-input-handler port input-fdport-handler size #t))
|
|
((eq? policy bufpol/line)
|
|
(error "bufpol/line not allowed on input"))
|
|
(else (error "policy not supported " policy))))
|
|
|
|
(define (install-input-handler port new-handler size gentle?)
|
|
(obtain-port-lock port)
|
|
(let* ((old-limit (port-limit port))
|
|
(old-index (port-index port))
|
|
(old-buffer (port-buffer port))
|
|
(old-unread (- old-limit old-index))
|
|
(new-unread (min old-unread size))
|
|
(throw-away (max 0 (- old-unread new-unread)))
|
|
(new-buffer (make-byte-vector size 0)))
|
|
(if (not gentle?)
|
|
(let ((ret (if (> throw-away 0)
|
|
(let ((return-buffer
|
|
(make-byte-vector throw-away 0)))
|
|
(copy-bytes! old-buffer old-index
|
|
return-buffer 0
|
|
throw-away) return-buffer)
|
|
#f)))
|
|
(copy-bytes! old-buffer (+ old-index throw-away)
|
|
new-buffer 0
|
|
new-unread)
|
|
(set-port-buffer! port new-buffer)
|
|
(set-port-index! port 0)
|
|
(set-port-limit! port new-unread)
|
|
(set-port-handler! port new-handler)
|
|
(release-port-lock port)
|
|
ret)
|
|
(begin
|
|
(install-drain-port-handler
|
|
old-buffer old-index old-limit port new-handler)
|
|
(set-port-buffer! port new-buffer)
|
|
(set-port-index! port 0)
|
|
(set-port-limit! port 0)
|
|
(release-port-lock port)
|
|
#t))))
|
|
|
|
(define (install-drain-port-handler
|
|
old-buffer old-start old-limit port new-handler)
|
|
(if (< 0 (- old-limit old-start))
|
|
(set-port-handler! port
|
|
(make-drain-port-handler
|
|
old-buffer old-start old-limit port new-handler))
|
|
(set-port-handler! port new-handler)))
|
|
|
|
|
|
;;; TODO: This reference to port will prevent gc !!!
|
|
(define (make-drain-port-handler
|
|
very-old-buffer old-start old-limit port new-handler)
|
|
(let ((old-buffer (make-byte-vector old-limit 0)))
|
|
(copy-bytes! very-old-buffer 0 old-buffer 0 old-limit)
|
|
(make-input-fdport-handler
|
|
(lambda (data buffer start needed)
|
|
(let ((old-left (- (byte-vector-length old-buffer) old-start)))
|
|
(let ((size (cond ((or (eq? needed 'any) (eq? needed 'immediate))
|
|
(min old-left
|
|
(byte-vector-length buffer)))
|
|
(else (min needed old-left)))))
|
|
(copy-bytes! old-buffer old-start buffer start size)
|
|
(set! old-start (+ size old-start))
|
|
|
|
(if (= old-start (byte-vector-length old-buffer)) ;buffer drained ?
|
|
(begin
|
|
(set-port-handler! port new-handler)
|
|
(if (and (integer? needed) (> needed size))
|
|
(+ size ((port-handler-buffer-proc new-handler)
|
|
data buffer (+ start size) (- needed size)))
|
|
size))
|
|
size)))))))
|
|
|
|
|
|
;;; Open & Close
|
|
;;; ------------
|
|
|
|
;;; replace rts/channel-port.scm begin
|
|
(define (open-file fname flags . maybe-mode)
|
|
(let ((fd (apply open-fdes fname flags maybe-mode))
|
|
(access (bitwise-and flags open/access-mask)))
|
|
((if (or (= access open/read) (= access open/read+write))
|
|
make-input-fdport
|
|
make-output-fdport)
|
|
fd 0)))
|
|
|
|
(define (open-input-file fname . maybe-flags)
|
|
(let ((flags (:optional maybe-flags 0)))
|
|
(open-file fname (deposit-bit-field flags open/access-mask open/read))))
|
|
|
|
(define (deposit-bit-field bits mask field)
|
|
(bitwise-ior (bitwise-and field mask)
|
|
(bitwise-and bits (bitwise-not mask))))
|
|
|
|
(define (open-output-file fname . rest)
|
|
(let* ((flags (if (pair? rest) (car rest)
|
|
(bitwise-ior open/create open/truncate))) ; default
|
|
(maybe-mode (if (null? rest) '() (cdr rest)))
|
|
(flags (deposit-bit-field flags open/access-mask open/write)))
|
|
(apply open-file fname flags maybe-mode)))
|
|
|
|
;;; replace rts/channel-port.scm end
|
|
|
|
;;; All these revealed-count-hacking procs have atomicity problems.
|
|
;;; They need to run uninterrupted.
|
|
;;; (port-locks should do the trick -df)
|
|
;;; (what else has atomicity problems? -df)
|
|
|
|
(define (increment-revealed-count port delta)
|
|
(obtain-port-lock port)
|
|
(let* ((data (fdport-data port))
|
|
(count (fdport-data:revealed data))
|
|
(newcount (+ count delta)))
|
|
(set-fdport-data:revealed data newcount)
|
|
(if (and (zero? count) (> newcount 0)) ; We just became revealed,
|
|
(begin
|
|
(strengthen-weak-table-ref fdports (fdport-data:fd data))
|
|
(%set-cloexec (fdport-data:fd data) #f)))); so don't close on exec().
|
|
(release-port-lock port))
|
|
|
|
(define (release-port-handle port)
|
|
(check-arg fdport? port port->fdes)
|
|
(obtain-port-lock port)
|
|
(let* ((data (fdport-data port))
|
|
(rev (fdport-data:revealed data)))
|
|
(if (not (zero? rev))
|
|
; (set-fdport-data:old-revealed data
|
|
; (- (fdport-data:old-revealed data) 1))
|
|
(let ((new-rev (- rev 1)))
|
|
(set-fdport-data:revealed data new-rev)
|
|
(if (zero? new-rev) ; We just became unrevealed, so
|
|
(begin ; the fd can be closed on exec.
|
|
(weaken-weak-table-ref fdports (fdport-data:fd data))
|
|
(%set-cloexec (fdport-data:fd data) #t))))))
|
|
(release-port-lock port))
|
|
|
|
(define (port-revealed port)
|
|
(let ((count (fdport-data:revealed
|
|
(fdport-data
|
|
(check-arg fdport? port port-revealed)))))
|
|
(and (not (zero? count)) count)))
|
|
|
|
(define (fdes->port fd port-maker) ; local proc.
|
|
(cond ((maybe-fdes->port fd) =>
|
|
(lambda (p)
|
|
(increment-revealed-count p 1)
|
|
p))
|
|
(else (port-maker fd 1))))
|
|
|
|
(define (fdes->inport fd)
|
|
(let ((port (fdes->port fd make-input-fdport)))
|
|
(if (not (input-port? port))
|
|
(error "fdes was already assigned to an outport" fd)
|
|
port)))
|
|
|
|
(define (fdes->outport fd)
|
|
(let ((port (fdes->port fd make-output-fdport)))
|
|
(if (not (output-port? port))
|
|
(error "fdes was already assigned to an inport" fd)
|
|
port)))
|
|
|
|
(define (port->fdes port)
|
|
(check-arg open-fdport? port port->fdes)
|
|
(let ((data (fdport-data port)))
|
|
(increment-revealed-count port 1)
|
|
(fdport-data:fd data)))
|
|
|
|
(define (call/fdes fd/port proc)
|
|
(cond ((integer? fd/port)
|
|
(proc fd/port))
|
|
|
|
((fdport? fd/port)
|
|
(let ((port fd/port))
|
|
(dynamic-wind
|
|
(lambda ()
|
|
(if (not port) (error "Can't throw back into call/fdes.")))
|
|
(lambda () (proc (port->fdes port)))
|
|
(lambda ()
|
|
(release-port-handle port)
|
|
(set! port #f)))))
|
|
|
|
(else (error "Not a file descriptor or fdport." fd/port))))
|
|
|
|
;;; Don't mess with the revealed count in the port case
|
|
;;; -- just sneakily grab the fdes and run.
|
|
|
|
(define (sleazy-call/fdes fd/port proc)
|
|
(proc (cond ((integer? fd/port) fd/port)
|
|
((fdport? fd/port) (fdport-data:fd (fdport-data fd/port)))
|
|
(else (error "Not a file descriptor or fdport." fd/port)))))
|
|
|
|
|
|
;;; Random predicates and arg checkers
|
|
;;; ----------------------------------
|
|
|
|
(define (open-fdport-data? x)
|
|
(and (fdport-data? x)
|
|
(not (fdport-data:closed? x))))
|
|
|
|
(define (open-fdport? x)
|
|
(and (fdport? x) (or (open-output-port? x) (open-input-port? x))))
|
|
|
|
(define (fdport-open? port)
|
|
(check-arg fdport? port fdport-open?)
|
|
(not (fdport-data:closed? (port-data port))))
|
|
|
|
|
|
;;; Initialise the system
|
|
;;; ---------------------
|
|
|
|
;;; JMG: should be deprecated-proc
|
|
(define error-output-port
|
|
current-error-port)
|
|
|
|
|
|
(define (init-fdports!)
|
|
(set-fluid! $current-input-port
|
|
(channel-port->input-fdport (current-input-port)))
|
|
(set-port-buffering (current-input-port) bufpol/none)
|
|
|
|
(set-fluid! $current-output-port
|
|
(channel-port->output-fdport (current-output-port)))
|
|
(set-fluid! $current-error-port
|
|
(channel-port->unbuffered-output-fdport (current-error-port)))
|
|
(set-fluid! $current-noise-port
|
|
(fluid $current-error-port)))
|
|
|
|
;;; Generic port operations
|
|
;;; -----------------------
|
|
|
|
;;; (close-after port f)
|
|
;;; Apply F to PORT. When F returns, close PORT, then return F's result.
|
|
;;; Does nothing special if you throw out or throw in.
|
|
|
|
(define (close-after port f)
|
|
(receive vals (f port)
|
|
(close port)
|
|
(apply values vals)))
|
|
|
|
(define (close port/fd)
|
|
((cond ((integer? port/fd) close-fdes)
|
|
((output-port? port/fd) close-output-port)
|
|
((input-port? port/fd) close-input-port)
|
|
(else (error "Not file-descriptor or port" port/fd))) port/fd))
|
|
|
|
;;; If this fd has an associated input or output port,
|
|
;;; move it to a new fd, freeing this one up.
|
|
|
|
(define (evict-ports fd)
|
|
(cond ((maybe-fdes->port fd) => ; Shouldn't bump the revealed count.
|
|
(lambda (port)
|
|
(%move-fdport (%dup fd) port 0)
|
|
#t))
|
|
(else #f)))
|
|
|
|
(define (%move-fdport fd port new-revealed)
|
|
(obtain-port-lock port)
|
|
(let* ((fdport* (fdport-data port))
|
|
(ch (fdport-data:channel fdport*))
|
|
(old-fd (channel-os-index ch))
|
|
(old-vector-ref (table-ref fdports old-fd)))
|
|
(set-fdport-data:revealed fdport* new-revealed)
|
|
(table-set! fdports old-fd #f)
|
|
(close-channel ch)
|
|
(set-fdport-data:channel
|
|
fdport*
|
|
(make-fd-channel port fd))
|
|
(table-set! fdports fd old-vector-ref)
|
|
(%set-cloexec fd (not new-revealed)))
|
|
(release-port-lock port)
|
|
#f) ; JMG: It used to return #f on succes in 0.5.1, so we do the same
|
|
|
|
(define (make-fd-channel port fd)
|
|
((if (input-port? port) make-input-fdchannel make-output-fdchannel) fd))
|
|
|
|
(define (close-fdes fd)
|
|
(if (evict-ports fd)
|
|
#t ; EBADF should not occur if there is a port
|
|
(%close-fdes fd)))
|
|
|
|
(define (flush-fdport fdport)
|
|
(check-arg fdport? fdport flush-fdport)
|
|
(force-output fdport))
|
|
|
|
(define (flush-all-ports)
|
|
(let ((thunks (output-port-forcers #f #f)))
|
|
(cond ((null? thunks)
|
|
#f)
|
|
(else
|
|
(let loop ((threads
|
|
(map spawn-thread thunks))
|
|
(new-threads '()))
|
|
(cond
|
|
((not (null? threads))
|
|
(if ((structure-ref threads-internal thread-continuation)
|
|
(car threads))
|
|
(loop (cdr threads)
|
|
(cons (car threads) new-threads))
|
|
(loop (cdr threads) new-threads)))
|
|
((not (null? new-threads))
|
|
(loop new-threads '()))))
|
|
#t))))
|
|
|
|
(define (spawn-thread thunk)
|
|
(let ((placeholder (make-placeholder)))
|
|
(spawn
|
|
(lambda ()
|
|
(placeholder-set!
|
|
placeholder
|
|
((structure-ref threads-internal current-thread)))
|
|
(thunk)))
|
|
(placeholder-value placeholder)))
|
|
|
|
(define (flush-all-ports-no-threads)
|
|
(let ((thunks (output-port-forcers #f #t)))
|
|
(for-each (lambda (thunk) (thunk)) thunks)))
|
|
|
|
;;; Extend R4RS i/o ops to handle file descriptors.
|
|
;;; -----------------------------------------------
|
|
|
|
(define s48-char-ready? (structure-ref scheme char-ready?))
|
|
(define s48-read-char (structure-ref scheme read-char))
|
|
|
|
(define-simple-syntax
|
|
(define-r4rs-input (name arg ...) stream s48name body ...)
|
|
(define (name arg ... . maybe-i/o)
|
|
(let ((stream (:optional maybe-i/o (current-input-port))))
|
|
(cond ((input-port? stream) (s48name arg ... stream))
|
|
((integer? stream) body ...)
|
|
(else (error "Not a port or file descriptor" stream))))))
|
|
|
|
(define-r4rs-input (char-ready?) input s48-char-ready?
|
|
(%char-ready-fdes? input))
|
|
|
|
(define-r4rs-input (read-char) input s48-read-char
|
|
(let ((port (fdes->inport input)))
|
|
(set-port-buffering port bufpol/none)
|
|
(s48-read-char port)))
|
|
|
|
;structure refs changed to get reference from scheme -dalbertz
|
|
(define s48-display (structure-ref scheme display))
|
|
(define s48-newline (structure-ref scheme newline))
|
|
(define s48-write (structure-ref scheme write))
|
|
(define s48-write-char (structure-ref scheme write-char))
|
|
(define s48-format (structure-ref formats format))
|
|
(define s48-force-output (structure-ref i/o force-output))
|
|
|
|
(define-simple-syntax
|
|
(define-r4rs-output (name arg ...) stream s48name body ...)
|
|
(define (name arg ... . maybe-i/o)
|
|
(let ((stream (:optional maybe-i/o (current-output-port))))
|
|
(cond ((output-port? stream) (s48name arg ... stream))
|
|
((integer? stream) body ...)
|
|
(else (error "Not an outport or file descriptor" stream))))))
|
|
|
|
;;; This one depends upon S48's string ports.
|
|
(define-r4rs-output (display object) output s48-display
|
|
(let ((sp (make-string-output-port)))
|
|
(display object sp)
|
|
(write-string (string-output-port-output sp) output)))
|
|
|
|
(define-r4rs-output (newline) output s48-newline
|
|
(let ((port (fdes->outport output)))
|
|
(set-port-buffering port bufpol/none)
|
|
(s48-newline port)))
|
|
|
|
(define-r4rs-output (write object) output s48-write
|
|
(let ((sp (make-string-output-port)))
|
|
(write object sp)
|
|
(write-string (string-output-port-output sp) output)))
|
|
|
|
(define-r4rs-output (write-char char) output s48-write-char
|
|
(let ((port (fdes->outport output)))
|
|
(set-port-buffering port bufpol/none)
|
|
(s48-write-char char port)))
|
|
|
|
;;; S48's force-output doesn't default to forcing (current-output-port).
|
|
(define-r4rs-output (force-output) output s48-force-output
|
|
(values)) ; Do nothing if applied to a file descriptor.
|
|
|
|
;;; extend channel-i/o's version to fdports
|
|
;;; WARNING: evil procedure, bypasses port-lock
|
|
(define (port->channel port)
|
|
(fdport-data:channel (fdport-data port)))
|
|
|
|
(define (format dest cstring . args)
|
|
(if (integer? dest)
|
|
(write-string (apply s48-format #f cstring args) dest)
|
|
(apply s48-format dest cstring args)))
|
|
|
|
;;; with-current-foo-port procs
|
|
;;; ---------------------------
|
|
|
|
(define (with-current-input-port* port thunk)
|
|
(let-fluid $current-input-port port thunk))
|
|
|
|
(define (with-current-output-port* port thunk)
|
|
(let-fluid $current-output-port port thunk))
|
|
|
|
(define (with-current-error-port* port thunk)
|
|
(let-fluid $current-error-port port thunk))
|
|
|
|
(define (with-error-output-port* port thunk)
|
|
(let-fluid $current-error-port port thunk))
|
|
|
|
(define-simple-syntax (with-current-input-port port body ...)
|
|
(with-current-input-port* port (lambda () body ...)))
|
|
|
|
(define-simple-syntax (with-current-output-port port body ...)
|
|
(with-current-output-port* port (lambda () body ...)))
|
|
|
|
(define-simple-syntax (with-current-error-port port body ...)
|
|
(with-current-error-port* port (lambda () body ...)))
|
|
|
|
(define-simple-syntax (with-error-output-port port body ...)
|
|
(with-error-output-port* port (lambda () body ...)))
|
|
|
|
;;; set-foo-port! procs
|
|
;;; -------------------
|
|
;;; Side-effecting variants of with-current-input-port* and friends.
|
|
|
|
(define (set-current-input-port! port) (set-fluid! $current-input-port port))
|
|
(define (set-current-output-port! port) (set-fluid! $current-output-port port))
|
|
(define (set-current-error-port! port) (set-fluid! $current-error-port port))
|
|
(define (set-error-output-port! port) (set-fluid! $current-error-port port))
|
|
|
|
|
|
;;; call-with-foo-file with-foo-to-file
|
|
;;; -----------------------------------
|
|
|
|
;;; Copied straight from rts/port.scm, but re-defined in this module,
|
|
;;; closed over my versions of open-input-file and open-output-file.
|
|
|
|
(define (call-with-mumble-file open close)
|
|
(lambda (string proc)
|
|
(let ((port #f))
|
|
(dynamic-wind (lambda ()
|
|
(if port
|
|
(warn "throwing back into a call-with-...put-file"
|
|
string)
|
|
(set! port (open string))))
|
|
(lambda () (proc port))
|
|
(lambda ()
|
|
(if port
|
|
(close port)))))))
|
|
|
|
;;; replace rts/channel-port.scm begin
|
|
(define call-with-input-file
|
|
(call-with-mumble-file open-input-file close-input-port))
|
|
|
|
(define call-with-output-file
|
|
(call-with-mumble-file open-output-file close-output-port))
|
|
|
|
(define (with-input-from-file string thunk)
|
|
(call-with-input-file string
|
|
(lambda (port)
|
|
(let-fluid $current-input-port port thunk))))
|
|
|
|
(define (with-output-to-file string thunk)
|
|
(call-with-output-file string
|
|
(lambda (port)
|
|
(let-fluid $current-output-port port thunk))))
|
|
|
|
;;; replace rts/channel-port.scm end
|
|
|
|
;;; select
|
|
;;; -----
|
|
|
|
(define (port/fdes->input-port port/fd)
|
|
(if (port? port/fd)
|
|
port/fd
|
|
(fdes->inport port/fd)))
|
|
|
|
(define (port/fdes->output-port port/fd)
|
|
(if (port? port/fd)
|
|
port/fd
|
|
(fdes->outport port/fd)))
|
|
|
|
(define (input-port/fdes-ready? port/fd)
|
|
(let ((port (port/fdes->input-port port/fd)))
|
|
((port-handler-ready? (port-handler port)) port)))
|
|
|
|
(define (output-port/fdes-ready? port/fd)
|
|
(let ((port (port/fdes->output-port port/fd)))
|
|
((port-handler-ready? (port-handler port)) port)))
|
|
|
|
(define (make-any-ready port/fdes-ready?)
|
|
(lambda (port/fds)
|
|
(let loop ((port/fds port/fds))
|
|
(if (null? port/fds)
|
|
'()
|
|
(let ((port/fd (car port/fds)))
|
|
(if (port/fdes-ready? port/fd)
|
|
;; one is ready, get them all
|
|
(let loop ((rest (cdr port/fds))
|
|
(ready (list port/fd)))
|
|
(cond
|
|
((null? rest) (reverse ready))
|
|
((port/fdes-ready? (car rest))
|
|
(loop (cdr rest) (cons (car rest) ready)))
|
|
(else
|
|
(loop (cdr rest) ready))))
|
|
(loop (cdr port/fds))))))))
|
|
|
|
(define any-input-ready (make-any-ready input-port/fdes-ready?))
|
|
(define any-output-ready (make-any-ready output-port/fdes-ready?))
|
|
|
|
(define any-channel-ready (make-any-ready fdport-channel-ready?))
|
|
|
|
(define (make-port/fdes-check-unlocked input?)
|
|
(let ((port/fdes->port
|
|
(if input?
|
|
port/fdes->input-port
|
|
port/fdes->output-port)))
|
|
(lambda (port/fd)
|
|
(if (port-locked? (port/fdes->port port/fd))
|
|
(begin
|
|
((structure-ref interrupts enable-interrupts!))
|
|
(error "SELECT on port with pending operation"
|
|
port/fd))))))
|
|
|
|
(define input-port/fdes-check-unlocked (make-port/fdes-check-unlocked #t))
|
|
(define output-port/fdes-check-unlocked (make-port/fdes-check-unlocked #f))
|
|
|
|
(define (port/fdes->input-channel port/fd)
|
|
(fdport-data:channel
|
|
(fdport-data
|
|
(port/fdes->input-port port/fd))))
|
|
|
|
(define (port/fdes->output-channel port/fd)
|
|
(fdport-data:channel
|
|
(fdport-data
|
|
(port/fdes->output-port port/fd))))
|
|
|
|
(define (select read-vec write-vec exception-vec . maybe-timeout)
|
|
(let ((read-list (vector->list read-vec))
|
|
(write-list (vector->list write-vec))
|
|
(timeout (:optional maybe-timeout #f)))
|
|
|
|
((structure-ref interrupts disable-interrupts!))
|
|
|
|
(for-each input-port/fdes-check-unlocked read-list)
|
|
(for-each output-port/fdes-check-unlocked write-list)
|
|
|
|
(let ((any-read (any-input-ready (filter input-port? read-list)))
|
|
(any-write (any-output-ready (filter output-port? write-list))))
|
|
|
|
(if (or (eqv? timeout 0) (pair? any-read) (pair? any-write))
|
|
(begin
|
|
((structure-ref interrupts enable-interrupts!))
|
|
(values (list->vector any-read)
|
|
(list->vector any-write)
|
|
(make-vector 0)))
|
|
|
|
;; we need to block
|
|
(let ((read-channels (map port/fdes->input-channel read-list))
|
|
(write-channels (map port/fdes->output-channel write-list)))
|
|
|
|
(for-each (lambda (channel)
|
|
(add-pending-channel channel #t))
|
|
read-channels)
|
|
|
|
(for-each (lambda (channel)
|
|
(add-pending-channel channel #f))
|
|
write-channels)
|
|
|
|
(call-with-values
|
|
(lambda ()
|
|
(wait-for-channels read-channels write-channels
|
|
(and timeout (* 1000 timeout))))
|
|
;; re-enables interrupts
|
|
(lambda (ready-read-channels ready-write-channels)
|
|
(let ((ready-read-port/fds '())
|
|
(ready-write-port/fds '()))
|
|
(for-each (lambda (port/fd channel)
|
|
(if (memq channel ready-read-channels)
|
|
(set! ready-read-port/fds
|
|
(cons port/fd ready-read-port/fds))))
|
|
read-list read-channels)
|
|
(for-each (lambda (port/fd channel)
|
|
(if (memq channel ready-write-channels)
|
|
(set! ready-write-port/fds
|
|
(cons port/fd ready-write-port/fds))))
|
|
write-list write-channels)
|
|
|
|
(values (list->vector (reverse ready-read-port/fds))
|
|
(list->vector (reverse ready-write-port/fds))
|
|
(make-vector 0))))))))))
|
|
|
|
(define (select! read-vec write-vec exception-vec . maybe-timeout)
|
|
(let ((read-list (vector->list read-vec))
|
|
(write-list (vector->list write-vec))
|
|
(timeout (:optional maybe-timeout #f)))
|
|
|
|
((structure-ref interrupts disable-interrupts!))
|
|
|
|
(for-each input-port/fdes-check-unlocked read-list)
|
|
(for-each output-port/fdes-check-unlocked write-list)
|
|
|
|
(let ((any-read (any-input-ready (filter input-port? read-list)))
|
|
(any-write (any-output-ready (filter output-port? write-list))))
|
|
(if (or (eqv? timeout 0) (pair? any-read) (pair? any-write))
|
|
(begin
|
|
((structure-ref interrupts enable-interrupts!))
|
|
(let ((n-read-ready
|
|
(let ((length (vector-length read-vec)))
|
|
(let loop ((i 0) (n 0))
|
|
(cond
|
|
((= i length) n)
|
|
((memq (vector-ref read-vec i) any-read)
|
|
(loop (+ 1 i) (+ 1 n)))
|
|
(else
|
|
(vector-set! read-vec i #f)
|
|
(loop (+ 1 i) n))))))
|
|
(n-write-ready
|
|
(let ((length (vector-length write-vec)))
|
|
(let loop ((i 0) (n 0))
|
|
(cond
|
|
((= i length) n)
|
|
((memq (vector-ref write-vec i) any-write)
|
|
(loop (+ 1 i) (+ 1 n)))
|
|
(else
|
|
(vector-set! write-vec i #f)
|
|
(loop (+ 1 i) n)))))))
|
|
|
|
;; zero out EXCEPTION-VEC
|
|
(let ((length (vector-length exception-vec)))
|
|
(let loop ((i 0))
|
|
(if (< i length)
|
|
(begin
|
|
(vector-set! exception-vec i #f)
|
|
(loop (+ 1 i))))))
|
|
|
|
(values n-read-ready n-write-ready 0)))
|
|
|
|
;; we need to block
|
|
(let ((read-channels (map port/fdes->input-channel read-list))
|
|
(write-channels (map port/fdes->output-channel write-list)))
|
|
|
|
(for-each (lambda (channel)
|
|
(add-pending-channel channel #t))
|
|
read-channels)
|
|
|
|
(for-each (lambda (channel)
|
|
(add-pending-channel channel #f))
|
|
write-channels)
|
|
|
|
(call-with-values
|
|
(lambda ()
|
|
(wait-for-channels read-channels write-channels
|
|
(and timeout (* 1000 timeout))))
|
|
;; re-enables interrupts
|
|
(lambda (ready-read-channels ready-write-channels)
|
|
|
|
(let ((n-read-ready
|
|
(let loop ((read-channels read-channels)
|
|
(n-ready 0)
|
|
(index 0))
|
|
(if (null? read-channels)
|
|
n-ready
|
|
(if (memq (car read-channels) ready-read-channels)
|
|
(loop (cdr read-channels)
|
|
(+ 1 n-ready)
|
|
(+ 1 index))
|
|
(begin
|
|
(vector-set! read-vec index #f)
|
|
(loop (cdr read-channels)
|
|
n-ready
|
|
(+ 1 index)))))))
|
|
(n-write-ready
|
|
(let loop ((write-channels write-channels)
|
|
(n-ready 0)
|
|
(index 0))
|
|
(if (null? write-channels)
|
|
n-ready
|
|
(if (memq (car write-channels) ready-write-channels)
|
|
(loop (cdr write-channels)
|
|
(+ 1 n-ready)
|
|
(+ 1 index))
|
|
(begin
|
|
(vector-set! write-vec index #f)
|
|
(loop (cdr write-channels)
|
|
n-ready
|
|
(+ 1 index))))))))
|
|
;; zero out EXCEPTION-VEC
|
|
(let ((length (vector-length exception-vec)))
|
|
(let loop ((i 0))
|
|
(if (< i length)
|
|
(begin
|
|
(vector-set! exception-vec i #f)
|
|
(loop (+ 1 i))))))
|
|
|
|
(values n-read-ready n-write-ready 0)))))))))
|
|
|
|
(define (verify-select-port-arguments! proc lst)
|
|
(for-each (lambda (p)
|
|
(or (input-port? p)
|
|
(output-port? p)
|
|
(call-error "Argument not a port" proc p)))
|
|
lst))
|
|
|
|
(define (select-ports timeout . ports)
|
|
(verify-select-port-arguments! select-ports ports)
|
|
(let ((read-list (filter input-port? ports))
|
|
(write-list (filter output-port? ports)))
|
|
|
|
((structure-ref interrupts disable-interrupts!))
|
|
|
|
(for-each input-port/fdes-check-unlocked read-list)
|
|
(for-each output-port/fdes-check-unlocked write-list)
|
|
|
|
(let ((any-read (any-input-ready read-list))
|
|
(any-write (any-output-ready write-list)))
|
|
|
|
(if (or (eqv? timeout 0) (pair? any-read) (pair? any-write))
|
|
(begin
|
|
((structure-ref interrupts enable-interrupts!))
|
|
(append any-read any-write))
|
|
|
|
(really-select-port-channels timeout read-list write-list)))))
|
|
|
|
(define (select-port-channels timeout . ports)
|
|
(verify-select-port-arguments! select-ports ports)
|
|
(let ((read-list (filter input-port? ports))
|
|
(write-list (filter output-port? ports)))
|
|
|
|
((structure-ref interrupts disable-interrupts!))
|
|
|
|
(for-each input-port/fdes-check-unlocked read-list)
|
|
(for-each output-port/fdes-check-unlocked write-list)
|
|
|
|
(let ((any-read (any-channel-ready read-list))
|
|
(any-write (any-channel-ready write-list)))
|
|
|
|
(if (or (eqv? timeout 0) (pair? any-read) (pair? any-write))
|
|
(begin
|
|
((structure-ref interrupts enable-interrupts!))
|
|
(append any-read any-write))
|
|
|
|
(really-select-port-channels timeout read-list write-list)))))
|
|
|
|
;; assumes interrupts are disabled and that ports aren't locked
|
|
|
|
(define (really-select-port-channels timeout read-list write-list)
|
|
(let ((read-channels (map port/fdes->input-channel read-list))
|
|
(write-channels (map port/fdes->output-channel write-list)))
|
|
|
|
(for-each (lambda (channel)
|
|
(add-pending-channel channel #t))
|
|
read-channels)
|
|
|
|
(for-each (lambda (channel)
|
|
(add-pending-channel channel #f))
|
|
write-channels)
|
|
|
|
(call-with-values
|
|
(lambda ()
|
|
(wait-for-channels read-channels write-channels
|
|
(and timeout (* 1000 timeout))))
|
|
;; re-enables interrupts
|
|
(lambda (ready-read-channels ready-write-channels)
|
|
(append (filter (lambda (read-port)
|
|
(any (lambda (read-channel)
|
|
(eq? read-channel
|
|
(port/fdes->input-channel read-port)))
|
|
ready-read-channels))
|
|
read-list)
|
|
(filter (lambda (write-port)
|
|
(any (lambda (write-channel)
|
|
(eq? write-channel
|
|
(port/fdes->output-channel write-port)))
|
|
ready-write-channels))
|
|
write-list))))))
|