;;; -*- lisp -*- ;;; ;;; ********************************************************************** ;;; This code was written by Paul Foley and has been placed in the public ;;; domain. ;;; ;;; Sbcl port by Rudi Schlatte. (in-package "SB-SIMPLE-STREAMS") ;;; ;;; ********************************************************************** ;;; ;;; Definition of File-Simple-Stream and relations (def-stream-class file-simple-stream (single-channel-simple-stream file-stream) ((pathname :initform nil :initarg :pathname) (filename :initform nil :initarg :filename) (original :initform nil :initarg :original) (delete-original :initform nil :initarg :delete-original))) (def-stream-class mapped-file-simple-stream (file-simple-stream direct-simple-stream) ()) (def-stream-class probe-simple-stream (simple-stream) ((pathname :initform nil :initarg :pathname))) (defmethod print-object ((object file-simple-stream) stream) (print-unreadable-object (object stream :type nil :identity nil) (with-stream-class (file-simple-stream object) (cond ((not (any-stream-instance-flags object :simple)) (princ "Invalid " stream)) ((not (any-stream-instance-flags object :input :output)) (princ "Closed " stream))) (format stream "~:(~A~) for ~S" (type-of object) (sm filename object))))) (defun open-file-stream (stream options) (let ((filename (pathname (getf options :filename))) (direction (getf options :direction :input)) (if-exists (getf options :if-exists)) (if-exists-given (not (eql (getf options :if-exists t) t))) (if-does-not-exist (getf options :if-does-not-exist)) (if-does-not-exist-given (not (eql (getf options :if-does-not-exist t) t)))) (with-stream-class (file-simple-stream stream) (ecase direction (:input (add-stream-instance-flags stream :input)) (:output (add-stream-instance-flags stream :output)) (:io (add-stream-instance-flags stream :input :output))) (cond ((and (sm input-handle stream) (sm output-handle stream) (not (eql (sm input-handle stream) (sm output-handle stream)))) (error "Input-Handle and Output-Handle can't be different.")) ((or (sm input-handle stream) (sm output-handle stream)) (add-stream-instance-flags stream :simple) ;; get namestring, etc., from handle, if possible ;; (i.e., if it's a stream) ;; set up buffers stream) (t (multiple-value-bind (fd namestring original delete-original) (%fd-open filename direction if-exists if-exists-given if-does-not-exist if-does-not-exist-given) (when fd (add-stream-instance-flags stream :simple) (setf (sm pathname stream) filename (sm filename stream) namestring (sm original stream) original (sm delete-original stream) delete-original) (when (any-stream-instance-flags stream :input) (setf (sm input-handle stream) fd)) (when (any-stream-instance-flags stream :output) (setf (sm output-handle stream) fd)) (sb-ext:finalize stream (lambda () (sb-unix:unix-close fd) (format *terminal-io* "~&;;; ** closed ~S (fd ~D)~%" namestring fd) (when original (revert-file namestring original))) :dont-save t) stream))))))) (defmethod device-open ((stream file-simple-stream) options) (with-stream-class (file-simple-stream stream) (when (open-file-stream stream options) ;; Franz says: ;; "The device-open method must be prepared to recognize resource ;; and change-class situations. If no filename is specified in ;; the options list, and if no input-handle or output-handle is ;; given, then the input-handle and output-handle slots should ;; be examined; if non-nil, that means the stream is still open, ;; and thus the operation being requested of device-open is a ;; change-class. Also, a device-open method need not allocate a ;; buffer every time it is called, but may instead reuse a ;; buffer it finds in a stream, if it does not become a security ;; issue." (unless (sm buffer stream) (let ((length (device-buffer-length stream))) (setf (sm buffer stream) (allocate-buffer length) (sm buffpos stream) 0 (sm buffer-ptr stream) 0 (sm buf-len stream) length))) (when (any-stream-instance-flags stream :output) (setf (sm control-out stream) *std-control-out-table*)) (setf (stream-external-format stream) (getf options :external-format :default)) stream))) ;;; Revert a file, if possible; otherwise just delete it. Used during ;;; CLOSE when the abort flag is set. ;;; ;;; TODO: use this in src/code/fd-stream.lisp:fd-stream-misc-routine ;;; as well, snarf error reporting from there. (defun revert-file (filename original) (declare (type simple-string filename) (type (or simple-string null) original)) ;; 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 original (multiple-value-bind (okay err) (sb-unix:unix-rename original filename) (unless okay (cerror "Go on as if nothing bad happened." "Could not restore ~S to its original contents: ~A" filename (sb-int:strerror err)))) ;; We can't restore the original, so nuke that puppy. (multiple-value-bind (okay err) (sb-unix:unix-unlink filename) (unless okay (cerror "Go on as if nothing bad happened." "Could not remove ~S: ~A" filename (sb-int:strerror err)))))) ;;; DELETE-ORIGINAL -- internal ;;; ;;; Delete a backup file. Used during CLOSE. ;;; ;;; TODO: use this in src/code/fd-stream.lisp:fd-stream-misc-routine ;;; as well, snarf error reporting from there. (defun delete-original (filename original) (declare (type simple-string filename) (type (or simple-string null) original)) (when original (multiple-value-bind (okay err) (sb-unix:unix-unlink original) (unless okay (cerror "Go on as if nothing bad happened." "Could not delete ~S during close of ~S: ~A" original filename (sb-int:strerror err)))))) (defmethod device-close ((stream file-simple-stream) abort) (with-stream-class (file-simple-stream stream) (let ((fd (or (sm input-handle stream) (sm output-handle stream))) (closed nil)) (when (integerp fd) (cond (abort (when (any-stream-instance-flags stream :output) #+win32 (progn (sb-unix:unix-close fd) (setf closed t)) (revert-file (sm filename stream) (sm original stream)))) (t (when (sm delete-original stream) (delete-original (sm filename stream) (sm original stream))))) (unless closed (sb-unix:unix-close fd))) (when (sm buffer stream) (free-buffer (sm buffer stream)) (setf (sm buffer stream) nil)))) t) (defmethod device-file-position ((stream file-simple-stream)) (with-stream-class (file-simple-stream stream) (let ((fd (or (sm input-handle stream) (sm output-handle stream)))) (if (integerp fd) (values (sb-unix:unix-lseek fd 0 sb-unix:l_incr)) (file-position fd))))) (defmethod (setf device-file-position) (value (stream file-simple-stream)) (declare (type fixnum value)) (with-stream-class (file-simple-stream stream) (let ((fd (or (sm input-handle stream) (sm output-handle stream)))) (if (integerp fd) (values (sb-unix:unix-lseek fd (if (minusp value) (1+ value) value) (if (minusp value) sb-unix:l_xtnd sb-unix:l_set))) (file-position fd value))))) (defmethod device-file-length ((stream file-simple-stream)) (with-stream-class (file-simple-stream stream) (let ((fd (or (sm input-handle stream) (sm output-handle stream)))) (if (integerp fd) (multiple-value-bind (okay dev ino mode nlink uid gid rdev size) (sb-unix:unix-fstat (sm input-handle stream)) (declare (ignore dev ino mode nlink uid gid rdev)) (if okay size nil)) (file-length fd))))) (defmethod device-open ((stream mapped-file-simple-stream) options) (with-stream-class (mapped-file-simple-stream stream) (when (open-file-stream stream options) (let* ((input (any-stream-instance-flags stream :input)) (output (any-stream-instance-flags stream :output)) (prot (logior (if input sb-posix::PROT-READ 0) (if output sb-posix::PROT-WRITE 0))) (fd (or (sm input-handle stream) (sm output-handle stream)))) (unless (integerp fd) (error "Can't memory-map an encapsulated stream.")) (multiple-value-bind (okay dev ino mode nlink uid gid rdev size) (sb-unix:unix-fstat fd) (declare (ignore ino mode nlink uid gid rdev)) (unless okay (sb-unix:unix-close fd) (sb-ext:cancel-finalization stream) (error "Error fstating ~S: ~A" stream (sb-int:strerror dev))) (when (>= size most-positive-fixnum) ;; Or else BUF-LEN has to be a general integer, or ;; maybe (unsigned-byte 32). In any case, this means ;; BUF-MAX and BUF-PTR have to be the same, which means ;; number-consing every time BUF-PTR moves... ;; Probably don't have the address space available to map ;; bigger files, anyway. Maybe DEVICE-READ can adjust ;; the mapped portion of the file when necessary? (warn "Unable to memory-map entire file.") (setf size (1- most-positive-fixnum))) (let ((buffer #-win32 (handler-case (sb-posix:mmap nil size prot sb-posix::MAP-SHARED fd 0) (sb-posix:syscall-error nil)) #+win32 (let ((mapping (sb-win32:create-file-mapping (sb-win32:get-osfhandle fd) nil 2 0 size nil))) (typecase mapping ((integer -1 0) nil) (t (let ((sap (prog1 (sb-win32:map-view-of-file mapping 4 0 0 size) (sb-win32:close-handle mapping)))) (and (not (zerop (sb-sys:sap-int sap))) sap))))))) (when (null buffer) (sb-unix:unix-close fd) (sb-ext:cancel-finalization stream) (error "Unable to map file.")) (setf (sm buffer stream) buffer (sm buffpos stream) 0 (sm buffer-ptr stream) size (sm buf-len stream) size) (when (any-stream-instance-flags stream :output) (setf (sm control-out stream) *std-control-out-table*)) (let ((efmt (getf options :external-format :default))) (compose-encapsulating-streams stream efmt) (setf (stream-external-format stream) efmt) ;; overwrite the strategy installed in :after method of ;; (setf stream-external-format) (install-single-channel-character-strategy (melding-stream stream) efmt 'mapped)) (sb-ext:finalize stream (lambda () #+win32 (sb-win32:unmap-view-of-file buffer) #-win32 (sb-posix:munmap buffer size) (format *terminal-io* "~&;;; ** unmapped ~S" buffer)) :dont-save t)))) stream))) (defmethod device-close ((stream mapped-file-simple-stream) abort) (with-stream-class (mapped-file-simple-stream stream) (when (sm buffer stream) #+win32 (sb-win32:unmap-view-of-file (sm buffer stream)) #-win32 (sb-posix:munmap (sm buffer stream) (sm buf-len stream)) (setf (sm buffer stream) nil)) (sb-unix:unix-close (or (sm input-handle stream) (sm output-handle stream)))) t) (defmethod device-write ((stream mapped-file-simple-stream) buffer start end blocking) (assert (eq buffer :flush) (buffer)) ; finish/force-output (with-stream-class (mapped-file-simple-stream stream) (sb-posix:msync (sm buffer stream) (sm buf-len stream) (if blocking sb-posix::ms-sync sb-posix::ms-async)))) (defmethod device-open ((stream probe-simple-stream) options) (let ((pathname (getf options :filename))) (with-stream-class (probe-simple-stream stream) (add-stream-instance-flags stream :simple) (when (sb-unix:unix-access (%file-namestring pathname) sb-unix:f_ok) (setf (sm pathname stream) pathname) t))))