;;; -*- Mode:LISP; Package:TAPE; Readtable:CL; Base:10. -*- ;;; ;;; Tar format support for Lambda. ;;; ;;; -dg 1/7/86 ;;; -kmc 1/88 bug fixes and mods: ;;; 1) Dummy transform on pathnames to specified host/dir (e.g. for relative ;;; pathname handling) ;;; 2) Use fake host "TAR-FORMAT" so user can specify to :find-file etc. (defvar *tar-default-record-size* 10240) ;;; Dummy host/pathname handling (defvar *dummy-unix-host-alist* nil) (defconst *dummy-unix-host-name* "TAR-FORMAT") (defvar *dummy-unix-host* nil) (add-initialization "Define TAR-Format dummy host" `(setq *dummy-unix-host* (progn (si:define-host *dummy-unix-host-name* :host-names (ncons *dummy-unix-host-name*) :machine-type :unix :system-type :unix :no-network nil) (si:parse-host *dummy-unix-host-name* t t))) '(:now) 'si:tape-warm-initialization-list) (defvar *default-dummy-transform-pathname* nil) (defun set-default-dummy-transform-pathname() (setq *default-dummy-transform-pathname* (let((default-dummy-transform-pathname (make-pathname :defaults (or *default-dummy-transform-pathname* (fs:user-homedir)) :name :wild :type :wild :version :highest))) (prompt-and-read `(:pathname :defaults ,default-dummy-transform-pathname :version :highest) (string-append "~&Specify pathname to provide host/directory for relative pathnames" "~% (default is ~a) : ") default-dummy-transform-pathname)))) (defun default-dummy-transform-pathname(pathname) (unless (typep *default-dummy-transform-pathname* 'pathname) (set-default-dummy-transform-pathname)) (make-pathname :defaults (merge-pathnames pathname *default-dummy-transform-pathname*) :host (pathname-host *default-dummy-transform-pathname*) :version :highest)) (defun dummy-transform-plist(plist) (cons (or (get plist :default-dummy-transform-pathname) (default-dummy-transform-pathname (car plist))) (cdr plist))) (defflavor tar-format ((record-size 10240) (file-stream) (op-mode) (ascii-translate? :determine-by-type) (buffer) (buffer-ptr 0) (buffer-fill 0) (eot nil) (tape-state-check) (*default-dummy-transform-pathname* *default-dummy-transform-pathname*)) (basic-tape-format) :gettable-instance-variables (:special-instance-variables *default-dummy-transform-pathname*)) (defmethod (tar-format :get-dummy-pathname) (pathname) (fs:parse-pathname pathname *dummy-unix-host*)) (defmethod (tar-format :get-pathname-from-plist) (plist) (let*((pathname (or (car plist) (get plist :pathname)))) (and pathname (send self :get-dummy-pathname pathname)))) (defmethod (tar-format :initialize) (&rest options) (send self :set-options :record-size *tar-default-record-size*) (when options (lexpr-send self :set-options options))) (defmethod (tar-format :set-options) (&rest options) (if options (do* ((list options (cddr list)) (option (car list) (car list)) (value (cadr list) (cadr list))) ((null list)) (case option (:record-size (if (zerop (remainder value 1024)) (setq record-size value) (signal 'invalid-option-value :object self :option option :value value))) (:ascii-translate? (if (typep value '(member :determine-by-type :always :query :never)) (setq ascii-translate? value) (signal 'invalid-option-value :object self :option option :value value))) (:default-dummy-transform-pathname (if (typep value 'pathname) (setq *default-dummy-transform-pathname* value) (signal 'invalid-option-value :object self :option option :value value))) (t (signal 'invalid-option :object self :option option :value value)))) (progn (setq *default-dummy-transform-pathname* (make-pathname :defaults (or *default-dummy-transform-pathname* (fs:user-homedir)) :name :wild :type :wild :version :highest)) (tv:choose-variable-values `((,(locf record-size) "Record Size" :number) (,(locf ascii-translate?) "Perform character set translation?" :choose (:determine-by-type :always :query :never)) (*default-dummy-transform-pathname* "Defaults for relative host/directory" :pathname *default-dummy-transform-pathname*)) :label '(:string "Options for the TAR tape format" :font fonts:tr12b))))) (defmethod (tar-format :read-tape-header) (&rest ignore)) (defmethod (tar-format :write-tape-header) (&rest ignore)) (defmethod (tar-format :tape-is-your-format-p) (device) (send device :rewind t) (condition-case () (send self :read-file-header device) ((logical-end-of-tape bad-file-header) (send self :rewind device) nil) (:no-error t))) (defmethod (tar-format :read-chunk) (device) (when eot (signal 'logical-end-of-tape :device-object device)) (unless buffer (setq buffer (allocate-resource 'si:dma-buffer (/ (send device :optimal-chunk-size record-size) 1024)))) (setq buffer-ptr 0 buffer-fill (* (condition-case (condition) (send device :read-array (si:dma-buffer-string buffer) (/ (length (si:dma-buffer-string buffer)) record-size) record-size) (filemark-encountered (setq eot t) (send condition :data-transferred))) record-size))) (defmethod (tar-format :write-chunk) (device) ;;; eot condition passed through (send device :write-array (si:dma-buffer-8b buffer) (/ (length (si:dma-buffer-8b buffer)) record-size) record-size) (setq buffer-ptr 0 buffer-fill (string-length (si:dma-buffer-string buffer)))) (defsubst read-from-string-octal (string start end) (parse-integer string :radix 8 :junk-allowed 't :start start :end end)) ;;; Tar file headers look like this: ;;; (total-size) := , , ... | ;;; name(100) := 100 bytes | 0 ;;; mode(8) := 6 bytes, space, null | 100 ;;; uid(8) := 6 bytes, space, null | 108 ;;; gid(8) := 6 bytes, space, null | 116 ;;; size(12) := 11 bytes, space | 124 ;;; date(12) := 11 bytes, space | 136 ;;; checksum(8) := 6 bytes, null,space | 148 ;;; link-flag(1) := 1 byte | 156 ;;; link-name(100) := 100 bytes | 157 ;;; filler(255) := 255 bytes(nulls) | 257 (defmethod (tar-format :read-file-header) (device) (case op-mode (nil (setq op-mode :read)) (:write (ferror 'read-during-write :device-object device)) (:read)) (when (or (null buffer) (= buffer-ptr buffer-fill)) (send self :read-chunk device)) (flet ((count-string (string) (do ((sum 0) (idx 0 (add1 idx))) ((= idx (string-length string)) sum) (incf sum (aref string idx))))) (let* ((string (nsubstring (si:dma-buffer-string buffer) buffer-ptr (+ buffer-ptr 512))) (checksum (read-from-string-octal string 148 155)) (sum (- (count-string string) (- (count-string (format nil "~6O~C " checksum #\center-dot)) (* 8 32))))) (unless (integerp checksum) (setq eot t) (signal 'logical-end-of-tape :device-object device)) (unless (= checksum sum) (signal 'bad-file-header :format-type (type-of self) :header (substring string 0))) (let* ((name (string-trim `(#\center-dot #\space) (nsubstring string 0 100))) (pathname (fs:parse-pathname name *dummy-unix-host*)) (length-in-bytes (read-from-string-octal string 124 135)) (creation-date (read-from-string-octal string 136 147)) (link-name (when (not (memq (char string 156) `(#\center-dot #\space))) (string-trim `(#\center-dot #\space) (nsubstring string 157 257)))) (byte-size (determine-pathname-byte-size pathname)) (characters (case ascii-translate? (:always t) (:never nil) (:query (y-or-n-p "~&Is \"~A\" a character file? ")) (:determine-by-type (translatable-file-p pathname))))) (if (nth-value 1 (ignore-errors (+ length-in-bytes creation-date checksum))) (and (setq eot t) (signal 'logical-end-of-tape :device-object device)) (progn (incf buffer-ptr 512) (list pathname :directory (fs:pathname-directory pathname) :name (fs:pathname-name pathname) :type (fs:pathname-type pathname) :version (fs:pathname-version pathname) :byte-size byte-size :characters characters :length-in-bytes (/ length-in-bytes (/ byte-size 8)) :creation-date (+ #.(time:parse-universal-time "JANUARY 1, 1970 GMT") creation-date) :link-name link-name :default-dummy-transform-pathname (default-dummy-transform-pathname pathname)))))))) (defmethod (tar-format :write-data-to-stream) (device stream length byte-size characters?) (let ((byte-factor (/ byte-size 8)) (array-to-use (ecase byte-size (8 (si:dma-buffer-8b buffer)) (16 (si:dma-buffer-16b buffer))))) (do* ((bytes-left length) (bytes-this-time (min bytes-left (/ (- buffer-fill buffer-ptr) byte-factor)) (min bytes-left (/ (- buffer-fill buffer-ptr) byte-factor))) (bptr (/ buffer-ptr byte-factor) (/ buffer-ptr byte-factor))) ((zerop bytes-left) (setq buffer-ptr (* (ceiling buffer-ptr 512) 512))) (when characters? (translate-ascii-to-lispm array-to-use :start bptr :end (+ bptr bytes-this-time))) (send stream :string-out array-to-use bptr (+ bptr bytes-this-time)) (when (= buffer-fill (incf buffer-ptr (* bytes-this-time byte-factor))) (send self :read-chunk device)) (decf bytes-left bytes-this-time)))) (defmethod (tar-format :read-data-from-stream) (device stream) (let* ((byte-factor (/ (get stream :byte-size) 8)) (array-to-use (ecase (get stream :byte-size) (8 (si:dma-buffer-8b buffer)) (16 (si:dma-buffer-16b buffer))))) (do* ((bytes-left (or (get stream :length-in-bytes) (get stream :length))) (bytes-this-time (min bytes-left (/ (- buffer-fill buffer-ptr) byte-factor)) (min bytes-left (/ (- buffer-fill buffer-ptr) byte-factor))) (bptr (/ buffer-ptr byte-factor) (/ buffer-ptr byte-factor))) ((zerop bytes-left) (setq buffer-ptr (* (ceiling buffer-ptr 512) 512))) (send stream :string-in nil array-to-use bptr (+ bptr bytes-this-time)) (when (get stream :characters) (translate-lispm-to-ascii array-to-use :start bptr :end (+ bptr bytes-this-time))) (when (= buffer-fill (incf buffer-ptr (* bytes-this-time byte-factor))) (send self :write-chunk device)) (decf bytes-left bytes-this-time)))) (defmethod (tar-format :space-to-end-of-this-file) (device plist bytes-passed) (let ((bytes (- (* (ceiling (* (get plist :length-in-bytes) (/ (get plist :byte-size) 8)) 512) 512) bytes-passed))) (if (<= bytes (- buffer-fill buffer-ptr)) (incf buffer-ptr bytes) (do ((bytes-left (- bytes (- buffer-fill buffer-ptr))) (ignore (send self :read-chunk device)) (buffer-length (length (si:dma-buffer-string buffer)))) ((zerop bytes-left)) (if (< bytes-left buffer-length) (setq buffer-ptr (+ buffer-ptr bytes-left) bytes-left 0) (progn(send self :read-chunk device) (decf bytes-left buffer-length))))))) (defmethod (tar-format :restore-file) (device &key transform query (overwrite :never) (create-directory :always) silent) (ecase op-mode (nil (setq op-mode :read)) (:write (ferror 'write-in-middle-of-tape :device-object device)) (:read)) (or *default-dummy-transform-pathname* (set-default-dummy-transform-pathname)) (when (= buffer-ptr buffer-fill) (send self :read-chunk device)) (let* ((plist (dummy-transform-plist (send self :read-file-header device)))) (if (null (get plist :link-name)) (let ((pathname (determine-restore-file-pathname plist transform overwrite query create-directory nil)) (length (get plist :length-in-bytes))) (if pathname (with-open-file (stream pathname :direction :output :byte-size (get plist :byte-size) :characters (get plist :characters)) (send self :write-data-to-stream device stream length (get plist :byte-size) (get plist :characters)) t) (send self :space-to-end-of-this-file device plist 0))) (progn (unless silent (format *standard-output* "~&File ~A is a link. Ignoring")) (send self :space-to-end-of-this-file device plist 0))))) (defmethod (tar-format :write-file-header) (device plist) (cond ((null buffer) (setq buffer (allocate-resource 'si:dma-buffer (/ (send device :optimal-chunk-size record-size) si:page-size 4)) buffer-ptr 0 buffer-fill (string-length (si:dma-buffer-string buffer)))) ((= buffer-ptr buffer-fill) (send self :write-chunk device))) (flet ((set-checksum (string &aux (checksum 0)) (dotimes (c (length string)) (incf checksum (aref string c))) (copy-array-portion (format nil "~6o~C " checksum #\center-dot) 0 8 string 148 156) string)) (let* ((name (send (send self :get-pathname-from-plist plist) :string-for-host)) (mode #o666) (uid 0) (gid 0) (string (format nil "~100,1,0,0A~6o ~C~6o ~C~6o ~C~11o ~11o ~8@t ~355,1,0,0A" name mode #\center-dot uid #\center-dot gid #\center-dot (* (get plist :length-in-bytes) (/ (get plist :byte-size) 8)) (- (get plist :creation-date) #.(time:parse-universal-time "JANUARY 1, 1970 GMT")) ""))) (copy-array-portion (set-checksum string) 0 512 (si:dma-buffer-string buffer) buffer-ptr (+ buffer-ptr 512)) (incf buffer-ptr 512)))) (defmethod (tar-format :write-file) (device file &key silent (end-of-tape-action :continue)) end-of-tape-action ;Tar format doesn't know about continue (with-open-file (fstream file :direction :input) (let* ((upn (let* ((tpn (send self :get-dummy-pathname (send fstream :truename))) (dir (fs:pathname-directory tpn))) (send tpn :new-directory (cons :relative (etypecase dir (list dir) (string (ncons dir))))))) (plist (list upn :byte-size (get fstream :byte-size) :characters (get fstream :characters) :length-in-bytes (or (get fstream :length-in-bytes) (get fstream :length)) :mode 0 :uid 0 :gid 0 :creation-date (get fstream :creation-date) :link-flag nil :link-name nil))) (unless silent (format *standard-output* "~&Writing file: ~A" (car plist))) (send self :write-file-header device plist) (send self :read-data-from-stream device fstream)))) (defmethod (tar-format :write-partition) (partition-name unit device) partition-name unit device (ferror 'not-supported :object self :operation :write-partition)) (defmethod (tar-format :compare-file) (device &key transform silent error-action) (declare(ignore error-action)) (case op-mode (nil (setq op-mode :read)) (:write (ferror 'read-during-write :device-object device)) (:read)) (let* ((plist (dummy-transform-plist (send self :read-file-header device))) (pathname (process-transform transform plist)) (byte-factor (/ (get plist :byte-size) 8)) (length (get plist :length-in-bytes))) (cond ((zerop length) (unless silent (format *standard-output* "~&File \"~A\" has zero length. Comparison assumed." pathname)) (rplaca plist t)) ((get plist :link-name) (unless silent (format *standard-output* "~&File \"~A\" is a link. Comparison assumed.")) (rplaca plist t)) ((not (condition-case (cond) (probef pathname) ((fs:file-not-found fs:directory-not-found)))) (unless silent (format *standard-output* "~&Source for dumped file \"~A\" not found. Skipping comparison." pathname)) (send self :space-to-end-of-this-file device plist 0) (rplaca plist :not-compared)) (t (with-open-file (f pathname :direction :input :byte-size (get plist :byte-size) :characters (get plist :characters)) (unless silent (format *standard-output* "~&Comparing \"~A\" with version on disk ... " pathname)) (if (/= length (or (get f :length-in-bytes) (get f :length))) (progn (unless silent (format *standard-output* "[*** Files are of unequal length or byte-size ***]")) (send self :space-to-end-of-this-file device plist 0) (rplaca plist nil)) (using-resource (compare-buffer si:dma-buffer (si:dma-buffer-size-in-pages buffer)) (let ((compare-array-to-use (ecase (get plist :byte-size) (8 (si:dma-buffer-8b compare-buffer)) (16 (si:dma-buffer-16b compare-buffer))))) (if (< (- buffer-fill buffer-ptr) (* (ceiling (* length byte-factor) 512) 512)) (do* ((bytes-left length) (bytes-this-time (min bytes-left (/ (- buffer-fill buffer-ptr) byte-factor)) (min bytes-left (/ (- buffer-fill buffer-ptr) byte-factor))) unequal) ((or (zerop bytes-left) unequal) (rplaca plist (unless unequal (unless silent (format *standard-output* "[Equal]")) (setq buffer-ptr (* (ceiling buffer-ptr 512) 512)) t))) (send f :string-in nil compare-array-to-use 0 bytes-this-time) (when (get plist :characters) (translate-lispm-to-ascii compare-array-to-use :end bytes-this-time)) (if (string-equal (si:dma-buffer-string compare-buffer) (si:dma-buffer-string buffer) :end1 (* bytes-this-time byte-factor) :start2 buffer-ptr :end2 (+ buffer-ptr (* bytes-this-time byte-factor))) (progn (incf buffer-ptr (* bytes-this-time byte-factor)) (decf bytes-left bytes-this-time) (when (= buffer-ptr buffer-fill) (send self :read-chunk device))) (progn (unless silent (format *standard-output* "[*** Unequal ***]")) (setq unequal t) (send self :space-to-end-of-this-file device plist (* (- length bytes-left) byte-factor))))) (progn (send f :string-in nil compare-array-to-use 0 length) (when (get plist :characters) (translate-lispm-to-ascii compare-array-to-use :end length)) (if (string-equal (si:dma-buffer-string compare-buffer) (si:dma-buffer-string buffer) :end1 (* length byte-factor) :start2 buffer-ptr :end2 (+ buffer-ptr (* length byte-factor))) (progn (incf buffer-ptr (* (ceiling (* length byte-factor) 512) 512)) (unless silent (format *standard-output* "[Equal]")) (rplaca plist t)) (progn (incf buffer-ptr (* (ceiling (* length byte-factor) 512) 512)) (unless silent (format *standard-output* "[*** Unequal ***]")) (rplaca plist nil))))))))))))) (defmethod (tar-format :beginning-of-file) (&rest ignore) (ferror nil "not yet implemented")) ; (device) (defmethod (tar-format :next-file) (&rest ignore) (ferror nil "not yet implemented")) ; (device &optional (nfiles 1)) (defmethod (tar-format :previous-file) (&rest ignore) (ferror nil "not yet implemented")) ; (device &optional (nfiles 1)) (defmethod (tar-format :find-file) (device match) (check-device device) (check-type match (or list compiled-function closure symbol string pathname)) (when (typep match '(or string pathname)) (setq match (pathname match)) (let((dir (pathname-directory match))) (setq match (make-pathname :defaults match :host *dummy-unix-host* :directory (if (and (stringp dir) (string-equal dir "*")) :wild dir))))) (do ((plist (send self :read-file-header device) (send self :read-file-header device))) ((tape-file-match match plist) (decf buffer-ptr 512) plist) (send self :space-to-end-of-this-file device plist 0))) (defmethod (tar-format :find-file-reverse) (&rest ignore) (signal 'not-supported :device-object self :operation :open-file)) (defmethod (tar-format :open-file) (device &key (direction :input) (byte-size :default) (characters :default) plist) (declare(ignore device direction byte-size characters plist)) (ferror nil "not yet implemented")) (defmethod (tar-format :list-files) (device &key (stream *standard-output*) (number-of-files -1)) (case op-mode (nil (setq op-mode :read)) (:write (ferror 'read-during-write :device-object device)) (:read)) (if eot (format stream "~&*** End of tape ***~%") (block tar-list-files (do (return-list (times 0 (add1 times)) plist) ((= times number-of-files) (nreverse return-list)) (condition-case () (let ((pl (send self :read-file-header device))) (remprop pl :checksum) ;There are not externally interesting (remprop pl :link-name) (push (setq plist pl) return-list)) (logical-end-of-tape (format stream "~&*** End of tape ***~%") (return-from tar-list-files (reverse return-list)))) (format stream "~&~A ~50TByte Size: ~D ~65T- Length in bytes: ~D" (car plist) (get plist :byte-size) (* (get plist :length-in-bytes) (/ (get plist :byte-size) 8))) (send self :space-to-end-of-this-file device plist 0))))) (defmethod (tar-format :finish-tape) (device) (let* ((buf (si:dma-buffer-8b buffer)) (records (ceiling buffer-ptr record-size)) (fptr (* records record-size))) (fill buf 0 :start buffer-ptr :end fptr) (send device :write-array buf records record-size) (send device :write-filemark) (setq eot t buffer nil buffer-ptr 0 buffer-fill 0 tape-state-check nil op-mode nil))) (defmethod (tar-format :rewind) (device &optional (wait-p t)) (when buffer (si:deallocate-resource 'si:dma-buffer buffer)) (setq buffer nil buffer-ptr 0 buffer-fill 0 eot nil tape-state-check nil op-mode nil) (send device :rewind wait-p)) (defmethod (tar-format :unload) (device) (when buffer (si:deallocate-resource 'si:dma-buffer buffer)) (setq buffer nil buffer-ptr 0 buffer-fill 0 eot nil tape-state-check nil op-mode nil) (send device :unload)) (defmethod (tar-format :position-to-append) ()) ; (device) (define-tape-format tar-format "TAR")