;;;; streams for UNIX file descriptors ;;;; This software is part of the SBCL system. See the README file for ;;;; more information. ;;;; ;;;; This software is derived from the CMU CL system, which was ;;;; written at Carnegie Mellon University and released into the ;;;; public domain. The software is in the public domain and is ;;;; provided with absolutely no warranty. See the COPYING and CREDITS ;;;; files for more information. (in-package "SB!IMPL") ;;; FIXME: Wouldn't it be clearer to just have the structure ;;; definition be DEFSTRUCT FILE-STREAM (instead of DEFSTRUCT ;;; FD-STREAM)? That way we'd have TYPE-OF and PRINT-OBJECT refer to ;;; these objects as FILE-STREAMs (the ANSI name) instead of the ;;; internal implementation name FD-STREAM, and there might be other ;;; benefits as well. (deftype file-stream () 'fd-stream) ;;;; buffer manipulation routines ;;; FIXME: Is it really good to maintain this pool separate from the ;;; GC and the C malloc logic? (defvar *available-buffers* () #!+sb-doc "List of available buffers. Each buffer is an sap pointing to bytes-per-buffer of memory.") (defconstant bytes-per-buffer (* 4 1024) #!+sb-doc "Number of bytes per buffer.") ;;; Return the next available buffer, creating one if necessary. #!-sb-fluid (declaim (inline next-available-buffer)) (defun next-available-buffer () (if *available-buffers* (pop *available-buffers*) (allocate-system-memory bytes-per-buffer))) ;;;; the FD-STREAM structure (defstruct (fd-stream (:constructor %make-fd-stream) (:include lisp-stream (misc #'fd-stream-misc-routine))) ;; the name of this stream (name nil) ;; the file this stream is for (file nil) ;; the backup file namestring for the old file, for :IF-EXISTS ;; :RENAME or :RENAME-AND-DELETE. (original nil :type (or simple-string null)) (delete-original nil) ; for :if-exists :rename-and-delete ;;; the number of bytes per element (element-size 1 :type index) ;; the type of element being transfered (element-type 'base-char) ;; the Unix file descriptor (fd -1 :type fixnum) ;; controls when the output buffer is flushed (buffering :full :type (member :full :line :none)) ;; character position (if known) (char-pos nil :type (or index null)) ;; T if input is waiting on FD. :EOF if we hit EOF. (listen nil :type (member nil t :eof)) ;; the input buffer (unread nil) (ibuf-sap nil :type (or system-area-pointer null)) (ibuf-length nil :type (or index null)) (ibuf-head 0 :type index) (ibuf-tail 0 :type index) ;; the output buffer (obuf-sap nil :type (or system-area-pointer null)) (obuf-length nil :type (or index null)) (obuf-tail 0 :type index) ;; output flushed, but not written due to non-blocking io? (output-later nil) (handler nil) ;; timeout specified for this stream, or NIL if none (timeout nil :type (or index null)) ;; pathname of the file this stream is opened to (returned by PATHNAME) (pathname nil :type (or pathname null))) (def!method print-object ((fd-stream fd-stream) stream) (declare (type stream stream)) (print-unreadable-object (fd-stream stream :type t :identity t) (format stream "for ~S" (fd-stream-name fd-stream)))) ;;;; output routines and related noise (defvar *output-routines* () #!+sb-doc "List of all available output routines. Each element is a list of the element-type output, the kind of buffering, the function name, and the number of bytes per element.") ;;; This is called by the server when we can write to the given file ;;; descriptor. Attempt to write the data again. If it worked, remove ;;; the data from the OUTPUT-LATER list. If it didn't work, something ;;; is wrong. (defun do-output-later (stream) (let* ((stuff (pop (fd-stream-output-later stream))) (base (car stuff)) (start (cadr stuff)) (end (caddr stuff)) (reuse-sap (cadddr stuff)) (length (- end start))) (declare (type index start end length)) (multiple-value-bind (count errno) (sb!unix:unix-write (fd-stream-fd stream) base start length) (cond ((not count) (if (= errno sb!unix:ewouldblock) (error "Write would have blocked, but SERVER told us to go.") (error "while writing ~S: ~A" stream (sb!unix:get-unix-error-msg errno)))) ((eql count length) ; Hot damn, it worked. (when reuse-sap (push base *available-buffers*))) ((not (null count)) ; Sorta worked. (push (list base (the index (+ start count)) end) (fd-stream-output-later stream)))))) (unless (fd-stream-output-later stream) (sb!sys:remove-fd-handler (fd-stream-handler stream)) (setf (fd-stream-handler stream) nil))) ;;; Arange to output the string when we can write on the file descriptor. (defun output-later (stream base start end reuse-sap) (cond ((null (fd-stream-output-later stream)) (setf (fd-stream-output-later stream) (list (list base start end reuse-sap))) (setf (fd-stream-handler stream) (sb!sys:add-fd-handler (fd-stream-fd stream) :output #'(lambda (fd) (declare (ignore fd)) (do-output-later stream))))) (t (nconc (fd-stream-output-later stream) (list (list base start end reuse-sap))))) (when reuse-sap (let ((new-buffer (next-available-buffer))) (setf (fd-stream-obuf-sap stream) new-buffer) (setf (fd-stream-obuf-length stream) bytes-per-buffer)))) ;;; Output the given noise. Check to see whether there are any pending ;;; writes. If so, just queue this one. Otherwise, try to write it. If ;;; this would block, queue it. (defun do-output (stream base start end reuse-sap) (declare (type fd-stream stream) (type (or system-area-pointer (simple-array * (*))) base) (type index start end)) (if (not (null (fd-stream-output-later stream))) ; something buffered. (progn (output-later stream base start end reuse-sap) ;; ### check to see whether any of this noise can be output ) (let ((length (- end start))) (multiple-value-bind (count errno) (sb!unix:unix-write (fd-stream-fd stream) base start length) (cond ((not count) (if (= errno sb!unix:ewouldblock) (output-later stream base start end reuse-sap) ;; FIXME: This and various other errors in this file ;; should probably be STREAM-ERROR. (error "while writing ~S: ~A" stream (sb!unix:get-unix-error-msg errno)))) ((not (eql count length)) (output-later stream base (the index (+ start count)) end reuse-sap))))))) ;;; Flush any data in the output buffer. (defun flush-output-buffer (stream) (let ((length (fd-stream-obuf-tail stream))) (unless (= length 0) (do-output stream (fd-stream-obuf-sap stream) 0 length t) (setf (fd-stream-obuf-tail stream) 0)))) ;;; Define output routines that output numbers size bytes long for the ;;; given bufferings. Use body to do the actual output. (defmacro def-output-routines ((name size &rest bufferings) &body body) (declare (optimize (speed 1))) (cons 'progn (mapcar #'(lambda (buffering) (let ((function (intern (let ((*print-case* :upcase)) (format nil name (car buffering)))))) `(progn (defun ,function (stream byte) ,(unless (eq (car buffering) :none) `(when (< (fd-stream-obuf-length stream) (+ (fd-stream-obuf-tail stream) ,size)) (flush-output-buffer stream))) ,@body (incf (fd-stream-obuf-tail stream) ,size) ,(ecase (car buffering) (:none `(flush-output-buffer stream)) (:line `(when (eq (char-code byte) (char-code #\Newline)) (flush-output-buffer stream))) (:full )) (values)) (setf *output-routines* (nconc *output-routines* ',(mapcar #'(lambda (type) (list type (car buffering) function size)) (cdr buffering))))))) bufferings))) (def-output-routines ("OUTPUT-CHAR-~A-BUFFERED" 1 (:none character) (:line character) (:full character)) (if (and (base-char-p byte) (char= byte #\Newline)) (setf (fd-stream-char-pos stream) 0) (incf (fd-stream-char-pos stream))) (setf (sap-ref-8 (fd-stream-obuf-sap stream) (fd-stream-obuf-tail stream)) (char-code byte))) (def-output-routines ("OUTPUT-UNSIGNED-BYTE-~A-BUFFERED" 1 (:none (unsigned-byte 8)) (:full (unsigned-byte 8))) (setf (sap-ref-8 (fd-stream-obuf-sap stream) (fd-stream-obuf-tail stream)) byte)) (def-output-routines ("OUTPUT-SIGNED-BYTE-~A-BUFFERED" 1 (:none (signed-byte 8)) (:full (signed-byte 8))) (setf (signed-sap-ref-8 (fd-stream-obuf-sap stream) (fd-stream-obuf-tail stream)) byte)) (def-output-routines ("OUTPUT-UNSIGNED-SHORT-~A-BUFFERED" 2 (:none (unsigned-byte 16)) (:full (unsigned-byte 16))) (setf (sap-ref-16 (fd-stream-obuf-sap stream) (fd-stream-obuf-tail stream)) byte)) (def-output-routines ("OUTPUT-SIGNED-SHORT-~A-BUFFERED" 2 (:none (signed-byte 16)) (:full (signed-byte 16))) (setf (signed-sap-ref-16 (fd-stream-obuf-sap stream) (fd-stream-obuf-tail stream)) byte)) (def-output-routines ("OUTPUT-UNSIGNED-LONG-~A-BUFFERED" 4 (:none (unsigned-byte 32)) (:full (unsigned-byte 32))) (setf (sap-ref-32 (fd-stream-obuf-sap stream) (fd-stream-obuf-tail stream)) byte)) (def-output-routines ("OUTPUT-SIGNED-LONG-~A-BUFFERED" 4 (:none (signed-byte 32)) (:full (signed-byte 32))) (setf (signed-sap-ref-32 (fd-stream-obuf-sap stream) (fd-stream-obuf-tail stream)) byte)) ;;; Do the actual output. If there is space to buffer the string, ;;; buffer it. If the string would normally fit in the buffer, but ;;; doesn't because of other stuff in the buffer, flush the old noise ;;; out of the buffer and put the string in it. Otherwise we have a ;;; very long string, so just send it directly (after flushing the ;;; buffer, of course). (defun output-raw-bytes (fd-stream thing &optional start end) #!+sb-doc "Output THING to FD-STREAM. THING can be any kind of vector or a SAP. If THING is a SAP, END must be supplied (as length won't work)." (let ((start (or start 0)) (end (or end (length (the (simple-array * (*)) thing))))) (declare (type index start end)) (let* ((len (fd-stream-obuf-length fd-stream)) (tail (fd-stream-obuf-tail fd-stream)) (space (- len tail)) (bytes (- end start)) (newtail (+ tail bytes))) (cond ((minusp bytes) ; error case (cerror "Just go on as if nothing happened." "~S called with :END before :START!" 'output-raw-bytes)) ((zerop bytes)) ; Easy case ((<= bytes space) (if (system-area-pointer-p thing) (system-area-copy thing (* start sb!vm:byte-bits) (fd-stream-obuf-sap fd-stream) (* tail sb!vm:byte-bits) (* bytes sb!vm:byte-bits)) ;; FIXME: There should be some type checking somewhere to ;; verify that THING here is a vector, not just . (copy-to-system-area thing (+ (* start sb!vm:byte-bits) (* sb!vm:vector-data-offset sb!vm:word-bits)) (fd-stream-obuf-sap fd-stream) (* tail sb!vm:byte-bits) (* bytes sb!vm:byte-bits))) (setf (fd-stream-obuf-tail fd-stream) newtail)) ((<= bytes len) (flush-output-buffer fd-stream) (if (system-area-pointer-p thing) (system-area-copy thing (* start sb!vm:byte-bits) (fd-stream-obuf-sap fd-stream) 0 (* bytes sb!vm:byte-bits)) ;; FIXME: There should be some type checking somewhere to ;; verify that THING here is a vector, not just . (copy-to-system-area thing (+ (* start sb!vm:byte-bits) (* sb!vm:vector-data-offset sb!vm:word-bits)) (fd-stream-obuf-sap fd-stream) 0 (* bytes sb!vm:byte-bits))) (setf (fd-stream-obuf-tail fd-stream) bytes)) (t (flush-output-buffer fd-stream) (do-output fd-stream thing start end nil)))))) ;;; the routine to use to output a string. If the stream is ;;; unbuffered, slam the string down the file descriptor, otherwise ;;; use OUTPUT-RAW-BYTES to buffer the string. Update charpos by ;;; checking to see where the last newline was. ;;; ;;; Note: some bozos (the FASL dumper) call write-string with things ;;; other than strings. Therefore, we must make sure we have a string ;;; before calling POSITION on it. ;;; KLUDGE: It would be better to fix the bozos instead of trying to ;;; cover for them here. -- WHN 20000203 (defun fd-sout (stream thing start end) (let ((start (or start 0)) (end (or end (length (the vector thing))))) (declare (fixnum start end)) (if (stringp thing) (let ((last-newline (and (find #\newline (the simple-string thing) :start start :end end) (position #\newline (the simple-string thing) :from-end t :start start :end end)))) (ecase (fd-stream-buffering stream) (:full (output-raw-bytes stream thing start end)) (:line (output-raw-bytes stream thing start end) (when last-newline (flush-output-buffer stream))) (:none (do-output stream thing start end nil))) (if last-newline (setf (fd-stream-char-pos stream) (- end last-newline 1)) (incf (fd-stream-char-pos stream) (- end start)))) (ecase (fd-stream-buffering stream) ((:line :full) (output-raw-bytes stream thing start end)) (:none (do-output stream thing start end nil)))))) ;;; Find an output routine to use given the type and buffering. Return ;;; as multiple values the routine, the real type transfered, and the ;;; number of bytes per element. (defun pick-output-routine (type buffering) (dolist (entry *output-routines*) (when (and (subtypep type (car entry)) (eq buffering (cadr entry))) (return (values (symbol-function (caddr entry)) (car entry) (cadddr entry)))))) ;;;; input routines and related noise (defvar *input-routines* () #!+sb-doc "List of all available input routines. Each element is a list of the element-type input, the function name, and the number of bytes per element.") ;;; Fill the input buffer, and return the first character. Throw to ;;; EOF-INPUT-CATCHER if the eof was reached. Drop into SYSTEM:SERVER ;;; if necessary. (defun do-input (stream) (let ((fd (fd-stream-fd stream)) (ibuf-sap (fd-stream-ibuf-sap stream)) (buflen (fd-stream-ibuf-length stream)) (head (fd-stream-ibuf-head stream)) (tail (fd-stream-ibuf-tail stream))) (declare (type index head tail)) (unless (zerop head) (cond ((eql head tail) (setf head 0) (setf tail 0) (setf (fd-stream-ibuf-head stream) 0) (setf (fd-stream-ibuf-tail stream) 0)) (t (decf tail head) (system-area-copy ibuf-sap (* head sb!vm:byte-bits) ibuf-sap 0 (* tail sb!vm:byte-bits)) (setf head 0) (setf (fd-stream-ibuf-head stream) 0) (setf (fd-stream-ibuf-tail stream) tail)))) (setf (fd-stream-listen stream) nil) (multiple-value-bind (count errno) ;; FIXME: Judging from compiler warnings, this WITH-ALIEN form expands ;; into something which uses the not-yet-defined type ;; (SB!ALIEN-INTERNALS:ALIEN (* (SB!ALIEN:STRUCT SB!UNIX:FD-SET))). ;; This is probably inefficient and unsafe and generally bad, so ;; try to find some way to make that type known before ;; this is compiled. (sb!alien:with-alien ((read-fds (sb!alien:struct sb!unix:fd-set))) (sb!unix:fd-zero read-fds) (sb!unix:fd-set fd read-fds) (sb!unix:unix-fast-select (1+ fd) (sb!alien:addr read-fds) nil nil 0 0)) (case count (1) (0 (unless #!-mp (sb!sys:wait-until-fd-usable fd :input (fd-stream-timeout stream)) #!+mp (sb!mp:process-wait-until-fd-usable fd :input (fd-stream-timeout stream)) (error 'io-timeout :stream stream :direction :read))) (t (error "problem checking to see whether ~S is readable: ~A" stream (sb!unix:get-unix-error-msg errno))))) (multiple-value-bind (count errno) (sb!unix:unix-read fd (sb!sys:int-sap (+ (sb!sys:sap-int ibuf-sap) tail)) (- buflen tail)) (cond ((null count) (if (eql errno sb!unix:ewouldblock) (progn (unless #!-mp (sb!sys:wait-until-fd-usable fd :input (fd-stream-timeout stream)) #!+mp (sb!mp:process-wait-until-fd-usable fd :input (fd-stream-timeout stream)) (error 'io-timeout :stream stream :direction :read)) (do-input stream)) (error "error reading ~S: ~A" stream (sb!unix:get-unix-error-msg errno)))) ((zerop count) (setf (fd-stream-listen stream) :eof) (throw 'eof-input-catcher nil)) (t (incf (fd-stream-ibuf-tail stream) count)))))) ;;; Make sure there are at least BYTES number of bytes in the input ;;; buffer. Keep calling DO-INPUT until that condition is met. (defmacro input-at-least (stream bytes) (let ((stream-var (gensym)) (bytes-var (gensym))) `(let ((,stream-var ,stream) (,bytes-var ,bytes)) (loop (when (>= (- (fd-stream-ibuf-tail ,stream-var) (fd-stream-ibuf-head ,stream-var)) ,bytes-var) (return)) (do-input ,stream-var))))) ;;; a macro to wrap around all input routines to handle EOF-ERROR noise (defmacro input-wrapper ((stream bytes eof-error eof-value) &body read-forms) (let ((stream-var (gensym)) (element-var (gensym))) `(let ((,stream-var ,stream)) (if (fd-stream-unread ,stream-var) (prog1 (fd-stream-unread ,stream-var) (setf (fd-stream-unread ,stream-var) nil) (setf (fd-stream-listen ,stream-var) nil)) (let ((,element-var (catch 'eof-input-catcher (input-at-least ,stream-var ,bytes) ,@read-forms))) (cond (,element-var (incf (fd-stream-ibuf-head ,stream-var) ,bytes) ,element-var) (t (eof-or-lose ,stream-var ,eof-error ,eof-value)))))))) (defmacro def-input-routine (name (type size sap head) &rest body) `(progn (defun ,name (stream eof-error eof-value) (input-wrapper (stream ,size eof-error eof-value) (let ((,sap (fd-stream-ibuf-sap stream)) (,head (fd-stream-ibuf-head stream))) ,@body))) (setf *input-routines* (nconc *input-routines* (list (list ',type ',name ',size)))))) ;;; STREAM-IN routine for reading a string char (def-input-routine input-character (character 1 sap head) (code-char (sap-ref-8 sap head))) ;;; STREAM-IN routine for reading an unsigned 8 bit number (def-input-routine input-unsigned-8bit-byte ((unsigned-byte 8) 1 sap head) (sap-ref-8 sap head)) ;;; STREAM-IN routine for reading a signed 8 bit number (def-input-routine input-signed-8bit-number ((signed-byte 8) 1 sap head) (signed-sap-ref-8 sap head)) ;;; STREAM-IN routine for reading an unsigned 16 bit number (def-input-routine input-unsigned-16bit-byte ((unsigned-byte 16) 2 sap head) (sap-ref-16 sap head)) ;;; STREAM-IN routine for reading a signed 16 bit number (def-input-routine input-signed-16bit-byte ((signed-byte 16) 2 sap head) (signed-sap-ref-16 sap head)) ;;; STREAM-IN routine for reading a unsigned 32 bit number (def-input-routine input-unsigned-32bit-byte ((unsigned-byte 32) 4 sap head) (sap-ref-32 sap head)) ;;; STREAM-IN routine for reading a signed 32 bit number (def-input-routine input-signed-32bit-byte ((signed-byte 32) 4 sap head) (signed-sap-ref-32 sap head)) ;;; Find an input routine to use given the type. Return as multiple ;;; values the routine, the real type transfered, and the number of ;;; bytes per element. (defun pick-input-routine (type) (dolist (entry *input-routines*) (when (subtypep type (car entry)) (return (values (symbol-function (cadr entry)) (car entry) (caddr entry)))))) ;;; Returns a string constructed from the sap, start, and end. (defun string-from-sap (sap start end) (declare (type index start end)) (let* ((length (- end start)) (string (make-string length))) (copy-from-system-area sap (* start sb!vm:byte-bits) string (* sb!vm:vector-data-offset sb!vm:word-bits) (* length sb!vm:byte-bits)) string)) ;;; the N-BIN method for FD-STREAMs. This blocks in UNIX-READ. It is ;;; generally used where there is a definite amount of reading to be ;;; done, so blocking isn't too problematical. (defun fd-stream-read-n-bytes (stream buffer start requested eof-error-p) (declare (type fd-stream stream)) (declare (type index start requested)) (do ((total-copied 0)) (nil) (declare (type index total-copied)) (let* ((remaining-request (- requested total-copied)) (head (fd-stream-ibuf-head stream)) (tail (fd-stream-ibuf-tail stream)) (available (- tail head)) (this-copy (min remaining-request available)) (this-start (+ start total-copied)) (sap (fd-stream-ibuf-sap stream))) (declare (type index remaining-request head tail available)) (declare (type index this-copy)) ;; Copy data from stream buffer into user's buffer. (if (typep buffer 'system-area-pointer) (system-area-copy sap (* head sb!vm:byte-bits) buffer (* this-start sb!vm:byte-bits) (* this-copy sb!vm:byte-bits)) (copy-from-system-area sap (* head sb!vm:byte-bits) buffer (+ (* this-start sb!vm:byte-bits) (* sb!vm:vector-data-offset sb!vm:word-bits)) (* this-copy sb!vm:byte-bits))) (incf (fd-stream-ibuf-head stream) this-copy) (incf total-copied this-copy) ;; Maybe we need to refill the stream buffer. (cond (;; If there were enough data in the stream buffer, we're done. (= total-copied requested) (return total-copied)) (;; If EOF, we're done in another way. (zerop (refill-fd-stream-buffer stream)) (if eof-error-p (error 'end-of-file :stream stream) (return total-copied))) ;; Otherwise we refilled the stream buffer, so fall ;; through into another pass of the loop. )))) ;;; Try to refill the stream buffer. Return the number of bytes read. ;;; (For EOF, the return value will be zero, otherwise positive.) (defun refill-fd-stream-buffer (stream) ;; We don't have any logic to preserve leftover bytes in the buffer, ;; so we should only be called when the buffer is empty. (assert (= (fd-stream-ibuf-head stream) (fd-stream-ibuf-tail stream))) (multiple-value-bind (count err) (sb!unix:unix-read (fd-stream-fd stream) (fd-stream-ibuf-sap stream) (fd-stream-ibuf-length stream)) (declare (type (or index null) count)) (when (null count) (error "error reading ~S: ~A" stream (sb!unix:get-unix-error-msg err))) (setf (fd-stream-listen stream) nil (fd-stream-ibuf-head stream) 0 (fd-stream-ibuf-tail stream) count) ; (format t "~%buffer=~%--~%") ; (dotimes (i count) ; (write-char (code-char (sap-ref-8 (fd-stream-ibuf-sap stream) i)))) ; (format t "~%--~%") #+nil (format t "/REFILL-FD-STREAM-BUFFER = ~D~%" count) count)) ;;;; utility functions (misc routines, etc) ;;; Fill in the various routine slots for the given type. INPUT-P and ;;; OUTPUT-P indicate what slots to fill. The buffering slot must be ;;; set prior to calling this routine. (defun set-routines (stream type input-p output-p buffer-p) (let ((target-type (case type ((:default unsigned-byte) '(unsigned-byte 8)) (signed-byte '(signed-byte 8)) (t type))) (input-type nil) (output-type nil) (input-size nil) (output-size nil)) (when (fd-stream-obuf-sap stream) (push (fd-stream-obuf-sap stream) *available-buffers*) (setf (fd-stream-obuf-sap stream) nil)) (when (fd-stream-ibuf-sap stream) (push (fd-stream-ibuf-sap stream) *available-buffers*) (setf (fd-stream-ibuf-sap stream) nil)) (when input-p (multiple-value-bind (routine type size) (pick-input-routine target-type) (unless routine (error "could not find any input routine for ~S" target-type)) (setf (fd-stream-ibuf-sap stream) (next-available-buffer)) (setf (fd-stream-ibuf-length stream) bytes-per-buffer) (setf (fd-stream-ibuf-tail stream) 0) (if (subtypep type 'character) (setf (fd-stream-in stream) routine (fd-stream-bin stream) #'ill-bin) (setf (fd-stream-in stream) #'ill-in (fd-stream-bin stream) routine)) (when (eql size 1) (setf (fd-stream-n-bin stream) #'fd-stream-read-n-bytes) (when buffer-p (setf (lisp-stream-in-buffer stream) (make-array in-buffer-length :element-type '(unsigned-byte 8))))) (setf input-size size) (setf input-type type))) (when output-p (multiple-value-bind (routine type size) (pick-output-routine target-type (fd-stream-buffering stream)) (unless routine (error "could not find any output routine for ~S buffered ~S" (fd-stream-buffering stream) target-type)) (setf (fd-stream-obuf-sap stream) (next-available-buffer)) (setf (fd-stream-obuf-length stream) bytes-per-buffer) (setf (fd-stream-obuf-tail stream) 0) (if (subtypep type 'character) (setf (fd-stream-out stream) routine (fd-stream-bout stream) #'ill-bout) (setf (fd-stream-out stream) (or (if (eql size 1) (pick-output-routine 'base-char (fd-stream-buffering stream))) #'ill-out) (fd-stream-bout stream) routine)) (setf (fd-stream-sout stream) (if (eql size 1) #'fd-sout #'ill-out)) (setf (fd-stream-char-pos stream) 0) (setf output-size size) (setf output-type type))) (when (and input-size output-size (not (eq input-size output-size))) (error "Element sizes for input (~S:~S) and output (~S:~S) differ?" input-type input-size output-type output-size)) (setf (fd-stream-element-size stream) (or input-size output-size)) (setf (fd-stream-element-type stream) (cond ((equal input-type output-type) input-type) ((null output-type) input-type) ((null input-type) output-type) ((subtypep input-type output-type) input-type) ((subtypep output-type input-type) output-type) (t (error "Input type (~S) and output type (~S) are unrelated?" input-type output-type)))))) ;;; Handle miscellaneous operations on fd-stream. (defun fd-stream-misc-routine (stream operation &optional arg1 arg2) (declare (ignore arg2)) ;; FIXME: Declare TYPE FD-STREAM STREAM? (case operation (:listen (or (not (eql (fd-stream-ibuf-head stream) (fd-stream-ibuf-tail stream))) (fd-stream-listen stream) (setf (fd-stream-listen stream) (eql (sb!alien:with-alien ((read-fds (sb!alien:struct sb!unix:fd-set))) (sb!unix:fd-zero read-fds) (sb!unix:fd-set (fd-stream-fd stream) read-fds) (sb!unix:unix-fast-select (1+ (fd-stream-fd stream)) (sb!alien:addr read-fds) nil nil 0 0)) 1)))) (:unread (setf (fd-stream-unread stream) arg1) (setf (fd-stream-listen stream) t)) (:close (cond (arg1 ;; We got us an abort on our hands. (when (fd-stream-handler stream) (sb!sys:remove-fd-handler (fd-stream-handler stream)) (setf (fd-stream-handler stream) nil)) (when (and (fd-stream-file stream) (fd-stream-obuf-sap stream)) ;; We can't do anything unless we know what file were ;; dealing with, and we don't want to do anything ;; strange unless we were writing to the file. (if (fd-stream-original stream) ;; We have a handle on the original, just revert. (multiple-value-bind (okay err) (sb!unix:unix-rename (fd-stream-original stream) (fd-stream-file stream)) (unless okay (cerror "Go on as if nothing bad happened." "could not restore ~S to its original contents: ~A" (fd-stream-file stream) (sb!unix:get-unix-error-msg err)))) ;; We can't restore the orignal, so nuke that puppy. (multiple-value-bind (okay err) (sb!unix:unix-unlink (fd-stream-file stream)) (unless okay (cerror "Go on as if nothing bad happened." "Could not remove ~S: ~A" (fd-stream-file stream) (sb!unix:get-unix-error-msg err))))))) (t (fd-stream-misc-routine stream :finish-output) (when (and (fd-stream-original stream) (fd-stream-delete-original stream)) (multiple-value-bind (okay err) (sb!unix:unix-unlink (fd-stream-original stream)) (unless okay (cerror "Go on as if nothing bad happened." "could not delete ~S during close of ~S: ~A" (fd-stream-original stream) stream (sb!unix:get-unix-error-msg err))))))) (when (fboundp 'cancel-finalization) (cancel-finalization stream)) (sb!unix:unix-close (fd-stream-fd stream)) (when (fd-stream-obuf-sap stream) (push (fd-stream-obuf-sap stream) *available-buffers*) (setf (fd-stream-obuf-sap stream) nil)) (when (fd-stream-ibuf-sap stream) (push (fd-stream-ibuf-sap stream) *available-buffers*) (setf (fd-stream-ibuf-sap stream) nil)) (sb!impl::set-closed-flame stream)) (:clear-input (setf (fd-stream-unread stream) nil) (setf (fd-stream-ibuf-head stream) 0) (setf (fd-stream-ibuf-tail stream) 0) (catch 'eof-input-catcher (loop (let ((count (sb!alien:with-alien ((read-fds (sb!alien:struct sb!unix:fd-set))) (sb!unix:fd-zero read-fds) (sb!unix:fd-set (fd-stream-fd stream) read-fds) (sb!unix:unix-fast-select (1+ (fd-stream-fd stream)) (sb!alien:addr read-fds) nil nil 0 0)))) (cond ((eql count 1) (do-input stream) (setf (fd-stream-ibuf-head stream) 0) (setf (fd-stream-ibuf-tail stream) 0)) (t (return t))))))) (:force-output (flush-output-buffer stream)) (:finish-output (flush-output-buffer stream) (do () ((null (fd-stream-output-later stream))) (sb!sys:serve-all-events))) (:element-type (fd-stream-element-type stream)) (:interactive-p ;; FIXME: sb!unix:unix-isatty is undefined. (sb!unix:unix-isatty (fd-stream-fd stream))) (:line-length 80) (:charpos (fd-stream-char-pos stream)) (:file-length (multiple-value-bind (okay dev ino mode nlink uid gid rdev size atime mtime ctime blksize blocks) (sb!unix:unix-fstat (fd-stream-fd stream)) (declare (ignore ino nlink uid gid rdev atime mtime ctime blksize blocks)) (unless okay (error "error fstat'ing ~S: ~A" stream (sb!unix:get-unix-error-msg dev))) (if (zerop mode) nil (truncate size (fd-stream-element-size stream))))) (:file-position (fd-stream-file-position stream arg1)))) (defun fd-stream-file-position (stream &optional newpos) (declare (type fd-stream stream) (type (or index (member nil :start :end)) newpos)) (if (null newpos) (sb!sys:without-interrupts ;; First, find the position of the UNIX file descriptor in the ;; file. (multiple-value-bind (posn errno) (sb!unix:unix-lseek (fd-stream-fd stream) 0 sb!unix:l_incr) (declare (type (or index null) posn)) (cond ((fixnump posn) ;; Adjust for buffered output: If there is any output ;; buffered, the *real* file position will be larger ;; than reported by lseek because lseek obviously ;; cannot take into account output we have not sent ;; yet. (dolist (later (fd-stream-output-later stream)) (incf posn (- (the index (caddr later)) (the index (cadr later))))) (incf posn (fd-stream-obuf-tail stream)) ;; Adjust for unread input: If there is any input ;; read from UNIX but not supplied to the user of the ;; stream, the *real* file position will smaller than ;; reported, because we want to look like the unread ;; stuff is still available. (decf posn (- (fd-stream-ibuf-tail stream) (fd-stream-ibuf-head stream))) (when (fd-stream-unread stream) (decf posn)) ;; Divide bytes by element size. (truncate posn (fd-stream-element-size stream))) ((eq errno sb!unix:espipe) nil) (t (sb!sys:with-interrupts (error "error LSEEK'ing ~S: ~A" stream (sb!unix:get-unix-error-msg errno))))))) (let ((offset 0) origin) (declare (type index offset)) ;; Make sure we don't have any output pending, because if we ;; move the file pointer before writing this stuff, it will be ;; written in the wrong location. (flush-output-buffer stream) (do () ((null (fd-stream-output-later stream))) (sb!sys:serve-all-events)) ;; Clear out any pending input to force the next read to go to ;; the disk. (setf (fd-stream-unread stream) nil) (setf (fd-stream-ibuf-head stream) 0) (setf (fd-stream-ibuf-tail stream) 0) ;; Trash cached value for listen, so that we check next time. (setf (fd-stream-listen stream) nil) ;; Now move it. (cond ((eq newpos :start) (setf offset 0 origin sb!unix:l_set)) ((eq newpos :end) (setf offset 0 origin sb!unix:l_xtnd)) ((typep newpos 'index) (setf offset (* newpos (fd-stream-element-size stream)) origin sb!unix:l_set)) (t (error "invalid position given to file-position: ~S" newpos))) (multiple-value-bind (posn errno) (sb!unix:unix-lseek (fd-stream-fd stream) offset origin) (cond ((typep posn 'fixnum) t) ((eq errno sb!unix:espipe) nil) (t (error "error lseek'ing ~S: ~A" stream (sb!unix:get-unix-error-msg errno)))))))) ;;;; creation routines (MAKE-FD-STREAM and OPEN) ;;; Returns a FD-STREAM on the given file. (defun make-fd-stream (fd &key (input nil input-p) (output nil output-p) (element-type 'base-char) (buffering :full) timeout file original delete-original pathname input-buffer-p (name (if file (format nil "file ~S" file) (format nil "descriptor ~D" fd))) auto-close) (declare (type index fd) (type (or index null) timeout) (type (member :none :line :full) buffering)) #!+sb-doc "Create a stream for the given unix file descriptor. If input is non-nil, allow input operations. If output is non-nil, allow output operations. If neither input nor output are specified, default to allowing input. Element-type indicates the element type to use (as for open). Buffering indicates the kind of buffering to use. Timeout (if true) is the number of seconds to wait for input. If NIL (the default), then wait forever. When we time out, we signal IO-TIMEOUT. File is the name of the file (will be returned by PATHNAME). Name is used to identify the stream when printed." (cond ((not (or input-p output-p)) (setf input t)) ((not (or input output)) (error "File descriptor must be opened either for input or output."))) (let ((stream (%make-fd-stream :fd fd :name name :file file :original original :delete-original delete-original :pathname pathname :buffering buffering :timeout timeout))) (set-routines stream element-type input output input-buffer-p) (when (and auto-close (fboundp 'finalize)) (finalize stream (lambda () (sb!unix:unix-close fd) #!+sb-show (format *terminal-io* "** closed file descriptor ~D **~%" fd)))) stream)) ;;; Pick a name to use for the backup file. (defvar *backup-extension* ".BAK" #!+sb-doc "This is a string that OPEN tacks on the end of a file namestring to produce a name for the :if-exists :rename-and-delete and :rename options. Also, this can be a function that takes a namestring and returns a complete namestring.") (defun pick-backup-name (name) (declare (type simple-string name)) (let ((ext *backup-extension*)) (etypecase ext (simple-string (concatenate 'simple-string name ext)) (function (funcall ext name))))) ;;; Ensure that the given arg is one of the given list of valid things. ;;; Allow the user to fix any problems. ;;; FIXME: Why let the user fix any problems? (defun ensure-one-of (item list what) (unless (member item list) (loop (cerror "Enter new value for ~*~S" "~S is invalid for ~S. Must be one of~{ ~S~}" item what list) (format (the stream *query-io*) "Enter new value for ~S: " what) (force-output *query-io*) (setf item (read *query-io*)) (when (member item list) (return)))) item) ;;; Rename NAMESTRING to ORIGINAL. First, check whether we have write ;;; access, since we don't want to trash unwritable files even if we ;;; technically can. We return true if we succeed in renaming. (defun do-old-rename (namestring original) (unless (sb!unix:unix-access namestring sb!unix:w_ok) (cerror "Try to rename it anyway." "File ~S is not writable." namestring)) (multiple-value-bind (okay err) (sb!unix:unix-rename namestring original) (cond (okay t) (t (cerror "Use :SUPERSEDE instead." "Could not rename ~S to ~S: ~A." namestring original (sb!unix:get-unix-error-msg err)) nil)))) (defun open (filename &key (direction :input) (element-type 'base-char) (if-exists nil if-exists-given) (if-does-not-exist nil if-does-not-exist-given) (external-format :default) &aux ; Squelch assignment warning. (direction direction) (if-does-not-exist if-does-not-exist) (if-exists if-exists)) #!+sb-doc "Return a stream which reads from or writes to Filename. Defined keywords: :direction - one of :input, :output, :io, or :probe :element-type - Type of object to read or write, default BASE-CHAR :if-exists - one of :error, :new-version, :rename, :rename-and-delete, :overwrite, :append, :supersede or nil :if-does-not-exist - one of :error, :create or nil See the manual for details." (unless (eq external-format :default) (error 'simple-error :format-control "Any external format other than :DEFAULT isn't recognized.")) ;; First, make sure that DIRECTION is valid. Allow it to be changed ;; if not. ;; ;; FIXME: Why allow it to be changed if not? (setf direction (ensure-one-of direction '(:input :output :io :probe) :direction)) ;; Calculate useful stuff. (multiple-value-bind (input output mask) (case direction (:input (values t nil sb!unix:o_rdonly)) (:output (values nil t sb!unix:o_wronly)) (:io (values t t sb!unix:o_rdwr)) (:probe (values t nil sb!unix:o_rdonly))) (declare (type index mask)) (let* ((pathname (pathname filename)) (namestring (cond ((unix-namestring pathname input)) ((and input (eq if-does-not-exist :create)) (unix-namestring pathname nil))))) ;; Process if-exists argument if we are doing any output. (cond (output (unless if-exists-given (setf if-exists (if (eq (pathname-version pathname) :newest) :new-version :error))) (setf if-exists ; FIXME: should just die, not allow resetting (ensure-one-of if-exists '(:error :new-version :rename :rename-and-delete :overwrite :append :supersede nil) :if-exists)) (case if-exists ((:error nil) (setf mask (logior mask sb!unix:o_excl))) ((:rename :rename-and-delete) (setf mask (logior mask sb!unix:o_creat))) ((:new-version :supersede) (setf mask (logior mask sb!unix:o_trunc))) (:append (setf mask (logior mask sb!unix:o_append))))) (t (setf if-exists :ignore-this-arg))) (unless if-does-not-exist-given (setf if-does-not-exist (cond ((eq direction :input) :error) ((and output (member if-exists '(:overwrite :append))) :error) ((eq direction :probe) nil) (t :create)))) (setf if-does-not-exist ; FIXME: should just die, not allow resetting (ensure-one-of if-does-not-exist '(:error :create nil) :if-does-not-exist)) (if (eq if-does-not-exist :create) (setf mask (logior mask sb!unix:o_creat))) (let ((original (if (member if-exists '(:rename :rename-and-delete)) (pick-backup-name namestring))) (delete-original (eq if-exists :rename-and-delete)) (mode #o666)) (when original ;; We are doing a :RENAME or :RENAME-AND-DELETE. ;; Determine whether the file already exists, make sure the original ;; file is not a directory, and keep the mode. (let ((exists (and namestring (multiple-value-bind (okay err/dev inode orig-mode) (sb!unix:unix-stat namestring) (declare (ignore inode) (type (or index null) orig-mode)) (cond (okay (when (and output (= (logand orig-mode #o170000) #o40000)) (error "cannot open ~S for output: is a directory" namestring)) (setf mode (logand orig-mode #o777)) t) ((eql err/dev sb!unix:enoent) nil) (t (error "cannot find ~S: ~A" namestring (sb!unix:get-unix-error-msg err/dev)))))))) (unless (and exists (do-old-rename namestring original)) (setf original nil) (setf delete-original nil) ;; In order to use :SUPERSEDE instead, we have to make sure ;; SB!UNIX:O_CREAT corresponds to IF-DOES-NOT-EXIST. ;; SB!UNIX:O_CREAT was set before because of IF-EXISTS being ;; :RENAME. (unless (eq if-does-not-exist :create) (setf mask (logior (logandc2 mask sb!unix:o_creat) sb!unix:o_trunc))) (setf if-exists :supersede)))) ;; Okay, now we can try the actual open. (loop (multiple-value-bind (fd errno) (if namestring (sb!unix:unix-open namestring mask mode) (values nil sb!unix:enoent)) (cond ((numberp fd) (return (case direction ((:input :output :io) (make-fd-stream fd :input input :output output :element-type element-type :file namestring :original original :delete-original delete-original :pathname pathname :input-buffer-p t :auto-close t)) (:probe (let ((stream (%make-fd-stream :name namestring :fd fd :pathname pathname :element-type element-type))) (close stream) stream))))) ((eql errno sb!unix:enoent) (case if-does-not-exist (:error (cerror "Return NIL." 'simple-file-error :pathname pathname :format-control "error opening ~S: ~A" :format-arguments (list pathname (sb!unix:get-unix-error-msg errno)))) (:create (cerror "Return NIL." 'simple-error :format-control "error creating ~S: Path does not exist." :format-arguments (list pathname)))) (return nil)) ((eql errno sb!unix:eexist) (unless (eq nil if-exists) (cerror "Return NIL." 'simple-file-error :pathname pathname :format-control "error opening ~S: ~A" :format-arguments (list pathname (sb!unix:get-unix-error-msg errno)))) (return nil)) ((eql errno sb!unix:eacces) (cerror "Try again." "error opening ~S: ~A" pathname (sb!unix:get-unix-error-msg errno))) (t (cerror "Return NIL." "error opening ~S: ~A" pathname (sb!unix:get-unix-error-msg errno)) (return nil))))))))) ;;;; initialization (defvar *tty* nil #!+sb-doc "The stream connected to the controlling terminal or NIL if there is none.") (defvar *stdin* nil #!+sb-doc "The stream connected to the standard input (file descriptor 0).") (defvar *stdout* nil #!+sb-doc "The stream connected to the standard output (file descriptor 1).") (defvar *stderr* nil #!+sb-doc "The stream connected to the standard error output (file descriptor 2).") ;;; This is called when the cold load is first started up, and may also ;;; be called in an attempt to recover from nested errors. (defun stream-cold-init-or-reset () (stream-reinit) (setf *terminal-io* (make-synonym-stream '*tty*)) (setf *standard-output* (make-synonym-stream '*stdout*)) (setf *standard-input* (#!-high-security ;; FIXME: Why is *STANDARD-INPUT* a TWO-WAY-STREAM? ANSI says it's ;; an input stream. make-two-way-stream #!+high-security %make-two-way-stream (make-synonym-stream '*stdin*) *standard-output*)) (setf *error-output* (make-synonym-stream '*stderr*)) (setf *query-io* (make-synonym-stream '*terminal-io*)) (setf *debug-io* *query-io*) (setf *trace-output* *standard-output*) nil) ;;; This is called whenever a saved core is restarted. (defun stream-reinit () (setf *available-buffers* nil) (setf *stdin* (make-fd-stream 0 :name "standard input" :input t :buffering :line)) (setf *stdout* (make-fd-stream 1 :name "standard output" :output t :buffering :line)) (setf *stderr* (make-fd-stream 2 :name "standard error" :output t :buffering :line)) (let ((tty (sb!unix:unix-open "/dev/tty" sb!unix:o_rdwr #o666))) (if tty (setf *tty* (make-fd-stream tty :name "the terminal" :input t :output t :buffering :line :auto-close t)) (setf *tty* (make-two-way-stream *stdin* *stdout*)))) nil) ;;;; beeping (defun default-beep-function (stream) (write-char (code-char bell-char-code) stream) (finish-output stream)) (defvar *beep-function* #'default-beep-function #!+sb-doc "This is called in BEEP to feep the user. It takes a stream.") (defun beep (&optional (stream *terminal-io*)) (funcall *beep-function* stream)) ;;; This is kind of like FILE-POSITION, but is an internal hack used ;;; by the filesys stuff to get and set the file name. (defun file-name (stream &optional new-name) (when (typep stream 'fd-stream) (cond (new-name (setf (fd-stream-pathname stream) new-name) (setf (fd-stream-file stream) (unix-namestring new-name nil)) t) (t (fd-stream-pathname stream))))) ;;;; international character support (which is trivial for our simple ;;;; character sets) ;;;; (Those who do Lisp only in English might not remember that ANSI ;;;; requires these functions to be exported from package ;;;; COMMON-LISP.) (defun file-string-length (stream object) (declare (type (or string character) object) (type file-stream stream)) #!+sb-doc "Return the delta in STREAM's FILE-POSITION that would be caused by writing Object to Stream. Non-trivial only in implementations that support international character sets." (declare (ignore stream)) (etypecase object (character 1) (string (length object)))) (defun stream-external-format (stream) (declare (type file-stream stream) (ignore stream)) #!+sb-doc "Return :DEFAULT." :default)