;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Package: STREAM -*- ;;; This code is in the public domain. ;;; The cmucl implementation of simple-streams was done by Paul Foley, ;;; who placed the code in the public domain. Sbcl port by Rudi ;;; Schlatte. (in-package "SB-SIMPLE-STREAMS") ;;; ;;; HELPER FUNCTIONS ;;; ;; All known stream flags. Note that the position in the constant ;; list is significant (cf. %flags below). (sb-int:defconstant-eqx +flag-bits+ '(:simple ; instance is valid :input :output ; direction :dual :string ; type of stream :eof ; latched EOF :dirty ; output buffer needs write :interactive) ; interactive stream #'equal) (eval-when (:compile-toplevel :load-toplevel :execute) (defun %flags (flags) (loop for flag in flags as pos = (position flag +flag-bits+) when (eq flag :gray) do (error "Gray streams not supported.") if pos sum (ash 1 pos) into bits else collect flag into unused finally (when unused (warn "Invalid stream instance flag~P: ~{~S~^, ~}" (length unused) unused)) (return bits)))) ;;; Setup an environment where sm, funcall-stm-handler and ;;; funcall-stm-handler-2 are valid and efficient for a stream of type ;;; class-name or for the stream argument (in which case the ;;; class-name argument is ignored). In nested with-stream-class ;;; forms, the inner with-stream-class form must specify a stream ;;; argument if the outer one specifies one, or the wrong object will ;;; be accessed. (defmacro with-stream-class ((class-name &optional stream) &body body) (if stream (let ((stm (gensym "STREAM")) (slt (gensym))) `(let* ((,stm ,stream) (,slt (sb-pcl::std-instance-slots ,stm))) (declare (type ,class-name ,stm) (ignorable ,slt)) (macrolet ((sm (slot-name stream) (declare (ignore stream)) (let ((slot-access (gethash slot-name *slot-access-functions*))) (cond ((sb-int:fixnump (cdr slot-access)) ;; Get value in nth slot `(the ,(car slot-access) (sb-pcl::clos-slots-ref ,',slt ,(cdr slot-access)))) (slot-access ;; Call memorized function `(the ,(car slot-access) (,(cdr slot-access) ,',stm))) (t ;; Use slot-value `(slot-value ,',stm ',slot-name))))) (add-stream-instance-flags (stream &rest flags) (declare (ignore stream)) `(setf (sm %flags ,',stm) (logior (sm %flags ,',stm) ,(%flags flags)))) (remove-stream-instance-flags (stream &rest flags) (declare (ignore stream)) `(setf (sm %flags ,',stm) (logandc2 (sm %flags ,',stm) ,(%flags flags)))) (any-stream-instance-flags (stream &rest flags) (declare (ignore stream)) `(not (zerop (logand (sm %flags ,',stm) ,(%flags flags)))))) ,@body))) `(macrolet ((sm (slot-name stream) (let ((slot-access (gethash slot-name *slot-access-functions*))) (cond ((sb-int:fixnump (cdr slot-access)) `(the ,(car slot-access) (sb-pcl::clos-slots-ref (sb-pcl::std-instance-slots ,stream) ,(cdr slot-access)))) (slot-access `(the ,(car slot-access) (,(cdr slot-access) ,stream))) (t `(slot-value ,stream ',slot-name)))))) ,@body))) (defmacro sm (slot-name stream) (let ((slot-access (gethash slot-name *slot-access-functions*))) (warn "Using ~S macro outside ~S" 'sm 'with-stream-class) (cond ((sb-int:fixnump (cdr slot-access)) `(the ,(car slot-access) (sb-pcl::clos-slots-ref (sb-pcl::std-instance-slots ,stream) ,(cdr slot-access)))) (slot-access `(the ,(car slot-access) (,(cdr slot-access) ,stream))) (t `(slot-value ,stream ',slot-name))))) (defmacro funcall-stm-handler (slot-name stream &rest args) (let ((s (gensym))) `(let ((,s ,stream)) (funcall (sm ,slot-name ,s) ,s ,@args)))) (defmacro funcall-stm-handler-2 (slot-name arg1 stream &rest args) (let ((s (gensym))) `(let ((,s ,stream)) (funcall (sm ,slot-name ,s) ,arg1 ,s ,@args)))) (defmacro add-stream-instance-flags (stream &rest flags) "Set the given flag bits in STREAM." (let ((s (gensym "STREAM"))) `(let ((,s ,stream)) (with-stream-class (simple-stream ,s) (setf (sm %flags ,s) (logior (sm %flags ,s) ,(%flags flags))))))) (defmacro remove-stream-instance-flags (stream &rest flags) "Clear the given flag bits in STREAM." (let ((s (gensym "STREAM"))) `(let ((,s ,stream)) (with-stream-class (simple-stream ,s) (setf (sm %flags ,s) (logandc2 (sm %flags ,s) ,(%flags flags))))))) (defmacro any-stream-instance-flags (stream &rest flags) "Determine whether any one of the FLAGS is set in STREAM." (let ((s (gensym "STREAM"))) `(let ((,s ,stream)) (with-stream-class (simple-stream ,s) (not (zerop (logand (sm %flags ,s) ,(%flags flags)))))))) (declaim (inline buffer-sap bref (setf bref) buffer-copy)) (defun buffer-sap (thing &optional offset) (declare (type simple-stream-buffer thing) (type (or fixnum null) offset) (optimize (speed 3) (space 2) (debug 0) (safety 0) ;; Suppress the note about having to box up the return: (sb-ext:inhibit-warnings 3))) (let ((sap (if (vectorp thing) (sb-sys:vector-sap thing) thing))) (if offset (sb-sys:sap+ sap offset) sap))) (defun bref (buffer index) (declare (type simple-stream-buffer buffer) (type (integer 0 #.most-positive-fixnum) index)) (sb-sys:sap-ref-8 (buffer-sap buffer) index)) (defun (setf bref) (octet buffer index) (declare (type (unsigned-byte 8) octet) (type simple-stream-buffer buffer) (type (integer 0 #.most-positive-fixnum) index)) (setf (sb-sys:sap-ref-8 (buffer-sap buffer) index) octet)) (defun buffer-copy (src soff dst doff length) (declare (type simple-stream-buffer src dst) (type fixnum soff doff length)) (sb-sys:without-gcing ;; is this necessary?? (sb-kernel:system-area-copy (buffer-sap src) (* soff 8) (buffer-sap dst) (* doff 8) (* length 8)))) (defun allocate-buffer (size) (if (= size sb-impl::bytes-per-buffer) (sb-impl::next-available-buffer) (make-array size :element-type '(unsigned-byte 8)))) (defun free-buffer (buffer) (when (not (vectorp buffer)) (push buffer sb-impl::*available-buffers*)) t) (defun %fd-open (pathname direction if-exists if-exists-given if-does-not-exist if-does-not-exist-given) (declare (type pathname pathname) (type (member :input :output :io :probe) direction) (type (member :error :new-version :rename :rename-and-delete :overwrite :append :supersede nil) if-exists) (type (member :error :create nil) if-does-not-exist)) (multiple-value-bind (input output mask) (ecase 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 sb-int:index mask)) (let ((name (cond ((sb-int:unix-namestring pathname input)) ((and input (eq if-does-not-exist :create)) (sb-int: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))) (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 nil))) ; :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)))) (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)) (sb-impl::pick-backup-name name) nil)) (delete-original (eq if-exists :rename-and-delete)) (mode #o666)) (when original ;; We are doing a :rename or :rename-and-delete. ;; Determine if the file already exists, make sure the original ;; file is not a directory and keep the mode (let ((exists (and name (multiple-value-bind (okay err/dev inode orig-mode) (sb-unix:unix-stat name) (declare (ignore inode) (type (or sb-int:index null) orig-mode)) (cond (okay (when (and output (= (logand orig-mode #o170000) #o40000)) (error 'sb-int:simple-file-error :pathname pathname :format-control "Cannot open ~S for output: Is a directory." :format-arguments (list name))) (setf mode (logand orig-mode #o777)) t) ((eql err/dev sb-unix:enoent) nil) (t (error 'sb-int:simple-file-error :pathname pathname :format-control "Cannot find ~S: ~A" :format-arguments (list name (sb-int:strerror err/dev))))))))) (unless (and exists (rename-file name original)) (setf original nil) (setf delete-original nil) ;; In order to use SUPERSEDE instead, we have ;; to make sure unix:o_creat corresponds to ;; if-does-not-exist. 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 name (sb-unix:unix-open name mask mode) (values nil sb-unix:enoent)) (cond ((sb-int:fixnump fd) (return (values fd name original delete-original))) ((eql errno sb-unix:enoent) (case if-does-not-exist (:error (cerror "Return NIL." 'sb-int:simple-file-error :pathname pathname :format-control "Error opening ~S, ~A." :format-arguments (list pathname (sb-int:strerror errno)))) (:create (cerror "Return NIL." 'sb-int:simple-file-error :pathname pathname :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." 'sb-int:simple-file-error :pathname pathname :format-control "Error opening ~S, ~A." :format-arguments (list pathname (sb-int:strerror errno)))) (return nil)) #+nil ; FIXME: reinstate this; error reporting is nice. ((eql errno sb-unix:eacces) (cerror "Try again." 'sb-int:simple-file-error :pathname pathname :format-control "Error opening ~S, ~A." :format-arguments (list pathname (sb-int:strerror errno)))) (t (cerror "Return NIL." 'sb-int:simple-file-error :pathname pathname :format-control "Error opening ~S, ~A." :format-arguments (list pathname (sb-int:strerror errno))) (return nil))))))))) (defun open-fd-stream (pathname &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)) (declare (type (or pathname string stream) pathname) (type (member :input :output :io :probe) direction) (type (member :error :new-version :rename :rename-and-delete :overwrite :append :supersede nil) if-exists) (type (member :error :create nil) if-does-not-exist) (ignore external-format)) (setq pathname (pathname pathname)) (multiple-value-bind (fd namestring original delete-original) (%fd-open pathname direction if-exists if-exists-given if-does-not-exist if-does-not-exist-given) (when fd (case direction ((:input :output :io) (sb-sys:make-fd-stream fd :input (member direction '(:input :io)) :output (member direction '(:output :io)) :element-type element-type :file namestring :original original :delete-original delete-original :pathname pathname :input-buffer-p t :auto-close t)) (:probe (let ((stream (sb-impl::%make-fd-stream :name namestring :fd fd :pathname pathname :element-type element-type))) (close stream) stream)))))) ;; Make PATHNAME and NAMESTRING work (defun cl::file-name (stream &optional new-name) (typecase stream (file-simple-stream (with-stream-class (file-simple-stream stream) (cond (new-name (setf (sm pathname stream) new-name) (setf (sm filename stream) (sb-int:unix-namestring new-name nil)) t) (t (sm pathname stream))))) (sb-sys::file-stream (cond (new-name (setf (sb-impl::fd-stream-pathname stream) new-name) (setf (sb-impl::fd-stream-file stream) (sb-int:unix-namestring new-name nil)) t) (t (sb-impl::fd-stream-pathname stream)))))) ;; Experimental "filespec" stuff ;; sat: Hooks to parse URIs etc apparently go here (defstruct (filespec-parser (:constructor make-filespec-parser (name priority function))) name priority function) (defvar *filespec-parsers* ()) (defun add-filespec (name priority function) (let ((filespec (make-filespec-parser name priority function))) (setf *filespec-parsers* (stable-sort (cons filespec (delete name *filespec-parsers* :key #'filespec-parser-name)) #'> :key #'filespec-parser-priority))) t) (defmacro define-filespec (name lambda-list &body body) (let ((truename (if (consp name) (first name) name)) (priority (if (consp name) (second name) 0))) `(add-filespec ',truename ,priority (lambda ,lambda-list (block ,truename ,@body))))) (defun parse-filespec (string &optional (errorp t)) (dolist (i *filespec-parsers* (when errorp (error "~S not recognised." string))) (let ((result (ignore-errors (funcall (filespec-parser-function i) string)))) (when result (return result))))) (define-filespec pathname (string) (pathname string))