;;; A Unix file port system to completely replace S48 file ports. ;;; We use S48 extensible ports. ;;; Copyright (c) 1993 by Olin Shivers. See file COPYING. (define-record fdport-data fd ; Unix file descriptor - integer. (closed? #f) ; Is port closed. (peek-char #f) revealed ; REVEALED & OLD-REVEALED are for keeping (old-revealed 0)) ; track of whether the FD value has escaped. ;;; We could flush the PEEK-CHAR field and use stdio ungetc(), but it ;;; is only guaranteed for buffered streams. Too bad... (define (alloc-input-fdport fd revealed) (make-extensible-input-port (make-fdport-data fd revealed) input-fdport-methods)) (define (alloc-output-fdport fd revealed) (make-extensible-output-port (make-fdport-data fd revealed) output-fdport-methods)) (define (make-input-fdport fd revealed) (let ((p (alloc-input-fdport fd revealed))) (%install-port fd p revealed) p)) (define (make-output-fdport fd revealed) (let ((p (alloc-output-fdport fd revealed))) (%install-port fd p revealed) p)) (define (fdport? x) (cond ((or (and (extensible-input-port? x) (extensible-input-port-local-data x)) (and (extensible-output-port? x) (extensible-output-port-local-data x))) => (lambda (d) (fdport-data? d))) (else #f))) ;;; Basic methods ;;; ------------- (define fdport-null-method (lambda (x) x #f)) ;;; CLOSE-FDPORT*, FLUSH-FDPORT* defined in syscalls.scm. ;;; (So you must load that file before loading this file.) (define (fdport*-read-char data) (check-arg open-fdport-data? data fdport*-read-char) (cond ((fdport-data:peek-char data) => (lambda (char) (set-fdport-data:peek-char data #f) char)) (else (or (%fdport*-read-char data) eof-object)))) (define (fdport*-peek-char data) (check-arg open-fdport-data? data fdport*-peek-char) (or (fdport-data:peek-char data) (cond ((%fdport*-read-char data) => (lambda (char) (set-fdport-data:peek-char data char) char)) (else eof-object)))) (define (fdport*-char-ready? data) (check-arg open-fdport-data? data fdport*-char-ready?) (or (fdport-data:peek-char data) (%fdport*-char-ready? data))) (define (fdport*-write-char data char) (check-arg open-fdport-data? data fdport*-write-char) (if (not (fdport-data:closed? data)) (%fdport*-write-char data char)) #f) ; Bogus fix -- otherwise %fdport*-...'s 0-value return blows up S48. (define (fdport*-write-string data string) (check-arg open-fdport-data? data fdport*-write-string) (generic-write-string string 0 (string-length string) ; from rw.scm write-fdport*-substring/errno data) #f) (define input-fdport-methods (make-input-port-methods close-fdport* fdport*-read-char fdport*-peek-char fdport*-char-ready? fdport-null-method ; current-column fdport-null-method)) ; current-row (define output-fdport-methods (make-output-port-methods close-fdport* fdport*-write-char fdport*-write-string (lambda (d) ; force output (flush-fdport* d) #f) ; bogus workaround. fdport-null-method ; fresh-line fdport-null-method ; current-column fdport-null-method)) ; current-row (define (fdport-data port) (let ((d ((cond ((extensible-input-port? port) extensible-input-port-local-data) ((extensible-output-port? port) extensible-output-port-local-data) (else (error "Illegal value" port))) port))) (if (and d (fdport-data? d)) d (error "fport closed" port)))) (define (%fdport-seek/errno port offset whence) (%fdport*-seek/errno (fdport-data port) offset whence)) (define (%fdport-tell/errno port) (%fdport*-tell/errno (fdport-data port))) (define (%fdport-set-buffering/errno port policy size) (%fdport*-set-buffering/errno (fdport-data port) policy size)) (define (set-port-buffering port policy . maybe-size) (let* ((size (if (pair? maybe-size) (if (pair? (cdr maybe-size)) (error "Too many arguments." set-port-buffering) (check-arg (lambda (s) (and (integer? s) (<= 0 s))) (car maybe-size) set-port-buffering)) -1)) (policy (if (zero? size) bufpol/none policy)) (err (%fdport-set-buffering/errno port policy size))) (if err (errno-error err set-port-buffering port policy size)))) ;;; Open & Close ;;; ------------ (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 (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))) ;;; All these revealed-count-hacking procs have atomicity problems. ;;; They need to run uninterrupted. (define (increment-revealed-count port delta) (let* ((data (extensible-port-local-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, (%set-cloexec (fdport-data:fd data) #f)))) ; so don't close on exec(). (define (release-port-handle port) (check-arg fdport? port port->fdes) (let* ((data (extensible-port-local-data port)) (rev (fdport-data:revealed data))) (if (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 (%set-cloexec (fdport-data:fd data) #t)))))); the fd can be closed on exec. (define (port-revealed port) (let ((count (fdport-data:revealed (extensible-port-local-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) (fdes->port fd make-input-fdport)) (define (fdes->outport fd) (fdes->port fd make-output-fdport)) (define (port->fdes port) (check-arg open-fdport? port port->fdes) (let ((data (extensible-port-local-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) (cond ((or (and (extensible-input-port? x) (extensible-input-port-local-data x)) (and (extensible-output-port? x) (extensible-output-port-local-data x))) => (lambda (d) (and (fdport-data? d) (not (fdport-data:closed? d))))) (else #f))) (define (extensible-port-local-data xport) ((if (extensible-input-port? xport) extensible-input-port-local-data extensible-output-port-local-data) xport)) (define (fdport-open? port) (check-arg fdport? port fdport-open?) (not (fdport-data:closed? (extensible-port-local-data port)))) ;;; Initialise the system ;;; --------------------- (define old-inport #f) ; Just because. (define old-outport #f) (define old-errport #f) (define (init-fdports!) (%init-fdports!) (if (not (fdport? (current-input-port))) (set! old-inport (current-input-port))) (if (not (fdport? (current-output-port))) (set! old-outport (current-output-port))) (if (not (fdport? (error-output-port))) (set! old-errport (error-output-port))) (let ((iport (fdes->inport 0))) (set-port-buffering iport bufpol/none) ; Stdin is unbuffered. (set-fluid! $current-input-port iport) (set-fluid! $current-output-port (fdes->outport 1)) (set-fluid! $error-output-port (fdes->outport 2)))) ;;; 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))))) (define (close-fdes fd) (evict-ports fd) (%close-fdes fd)) ;;; 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 (read-fdes-char input)) ;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 ports 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 a port 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 (write-fdes-char #\newline output)) (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 (write-fdes-char char output)) ;;; 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. (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-error-output-port* port thunk) (let-fluid $error-output-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-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-error-output-port! port) (set-fluid! $error-output-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))))))) (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))))