;;; -*- Mode:LISP; Package:USER; Readtable:ZL; Base:10 -*- ;;; TAR Tape Archiver, after the Unix(TM) command of the same name. ;;; use READ-UID-ALIST first if you want correct file author (UID) handling. ;;; or setq *UID-ALIST* ;;; Copyright LISP Machine, Inc. 1986 ;;; See filename "Copyright.Text" for ;;; licensing and release information. ;;; ;;; ********************************************************* ;;; ********************************************************* ;;; *** NOTE: This is an EXAMPLE, not LMI supported code. *** ;;; *** information contained in this example is subject *** ;;; *** to change without notice. The ways of doing *** ;;; *** the things contained in the example may change *** ;;; *** between system releases. Some techniques which *** ;;; *** are mere examples in one release may become built *** ;;; *** in system features in the next release. Use good *** ;;; *** judgement when copying these techniques. Most *** ;;; *** examples have been motivated by specific customer *** ;;; *** requests, and may not be the best engineered *** ;;; *** or most efficient solution for someone else. *** ;;; ********************************************************* ;;; ********************************************************* ;;; ;;; There have been various implementations of this handy utility, ;;; but this aught to do it for good. 19-Dec-86 17:51:16 -gjc ;;; set this to NIL if you are going to be dealing with binary files. (defvar *lispm-character-translate* t) (defvar *tblock* 512) (defvar *namsiz* 100) (defvar *hblock* (let ((i 0)) (mapcar #'(lambda (b) (append b (list i (incf i (cadr b))))) `((name ,*namsiz* asciz-string) (mode 8 octal-int) (uid 8 octal-int) (gid 8 octal-int) (size 12 octal-int) (mtime 12 octal-int) (chksum 8 octal-int-checksum) (linkflag 1 a-character) (linkname ,*namsiz* asciz-string))))) (defun parse-hblock (string) (cond ((zerop (aref string 0)) ()) ('else (let ((e (assq 'chksum *hblock*))) (funcall (nth 2 e) string (nth 3 e) (nth 4 e))) (let ((alist nil)) (dolist (e *hblock*) (unless (eq (car e) 'chksum) (push (list (car e) (funcall (nth 2 e) string (nth 3 e) (nth 4 e))) alist))) alist)))) (defun string-in (stream st) (values st (send stream :string-in nil st))) (defun octal-int-checksum (string start end) (let ((chksum (octal-int string start end))) (copy-array-portion " " 0 8 string start end) (do ((sum 0 (+ (aref string j) sum)) (j 0 (1+ j)) (n (length string))) ((= j n) (or (= sum chksum) (ferror nil "directory checksum error")) sum)))) (defun asciz-string (st start end) (substring st start (or (string-search-char 0 st start end) end))) (defun octal-int (st start end) (parse-integer st :radix 8 :start start :end (or (string-search-char 0 st start end) end))) (defun a-character (st start end) end (aref st start)) (defvar *verbose* nil) (defvar *filter* nil) (defun tar (&optional &key table verbose create (extract (and (not create) (not table))) file (blocksize 20) (character-translate t) no-rewind) "Like the unix(tm) tar command" (if (and extract create) (ferror nil "cant both extract and create at same time")) (let ((*verbose* (or table verbose)) (*filter* extract) (*lispm-character-translate* character-translate)) (cond ((and file (not (mem #'string-equal file '("MT" "MT:")))) (cond (create (with-open-file (stream file :direction :output) (tar-create stream create))) ('else (with-open-file (stream file) (tar-extract stream))))) ('else (or no-rewind (fs:mt-rewind)) (let (s) (cond (create (unwind-protect (tar-create (setq s (fs:make-mt-stream :direction :output :record-size (* blocksize *tblock*))) create) (and s (send s :close))) (fs:mt-write-eof)) ('else (unwind-protect (tar-extract (setq s (fs:make-mt-stream :record-size (* blocksize *tblock*)))) ;; use close abort so that the driver wont seek to the EOF marker. (and s (send s :close t)))))))))) (DEFVAR *UID-ALIST* NIL) (defun tar-extract (stream) (let ((tbuf (make-string *tblock*))) (do ((dir)(file)) ((not (setq dir (parse-hblock (string-in stream tbuf))))) (let ((name (cadr (assq 'name dir))) (size (cadr (assq 'size dir))) (linkp (cadr (assq 'linkflag dir)))) (when *verbose* (case linkp (#\1 (format t "~&~A linked to ~A~%" name (cadr (assq 'linkname dir)))) (#\2 (format t "~&~A symbolic link to ~A~%" name (cadr (assq 'linkname dir)))) (t (format t "~&~A ~D bytes~%" name size))) (when (eq *verbose* :verbose) (dolist (d dir) (format t "~8A = ~S~%" (car d) (cond ((eq (car d) 'mtime) (time:print-universal-time (unix-time->universal-time (cadr d)) nil)) ((and (eq (car d) 'uid) (cadr (assq (cadr d) *uid-alist*)))) ('else (cadr d))))))) (cond ((or (and (not (zerop (length name))) (= #// (aref name (1- (length name))))) (member linkp '(#\1 #\2)) (not (setq file (filter-filename name)))) (cond ((= #\1 linkp)) ('else (multiple-value-bind (nblocks left) (floor size *tblock*) (dotimes (j nblocks) (string-in stream tbuf)) (or (zerop left) (string-in stream tbuf)))))) ('else (with-open-file (ostream file :direction :output) (SEND OSTREAM :CHANGE-PROPERTIES NIL :CREATION-DATE (unix-time->universal-time (cadr (assq 'mtime dir))) :AUTHOR (or (cadr (assoc (cadr (assq 'uid dir)) *uid-alist*)) si:user-id)) (multiple-value-bind (nblocks left) (floor size *tblock*) (dotimes (j nblocks) (string-in stream tbuf) (unix->lispm-translate tbuf) (send ostream :string-out tbuf)) (unless (zerop left) (string-in stream tbuf) (unix->lispm-translate tbuf) (send ostream :string-out tbuf 0 left)))))))))) (defvar dummy-unix-host (si:make-dummy-host :unix)) (defun filter-filename (name) (cond ((not *filter*) nil) ('else (let ((pathname (fs:parse-pathname (string-append "//" name) dummy-unix-host))) (cond ((eq *filter* t) (send pathname :new-pathname :host si:local-host)) ((or (stringp *filter*) (typep *filter* 'si:host)) (send pathname :new-pathname :host *filter*)) ('else (funcall *filter* pathname name))))))) (defun tmp-query-filter (pathname name) name (let ((p (send pathname :new-pathname :host si:local-host :directory "TMP"))) (when (y-or-n-p "~&Restore ~A ?" pathname) p))) (defun unix->lispm-translate (s) (when *lispm-character-translate* (macrolet ((utrans (&rest l) `(cond ,@(mapcan #'(lambda (c) (let (cfrom cto) (cond ((atom c) (setq cfrom (- c #o200)) (setq cto c)) ('else (setq cfrom (- (car c) #o200)) (setq cto (cadr c)))) (list `((= c ,cfrom) (setf (aref s j) ,cto)) `((= c ,cto) (setf (aref s j) ,cfrom))))) l)))) (dotimes (j (length s)) (let ((c (aref s j))) (utrans (#\line #\return) #\tab #\form)))))) (defvar *unix-base-ut* (time:parse-universal-time "Midnight Jan 1,1970")) (defun unix-time->universal-time (x) (+ x *unix-base-ut*)) (defun universal-time->unix-time (x) (- x *unix-base-ut*)) (defun read-uid-alist (from-host &optional (from-file "//etc//passwd")) ;; username:password:uid:gid:fullname:dir:shell (with-open-file (stream (fs:parse-pathname from-file from-host)) (do ((line) (alist)) ((null (setq line (readline stream nil))) (setq *uid-alist* (nreverse alist))) (cond ((zerop (length line))) ('else (let ((i (string-search-char #\: line)) (name)(uid)) (setq name (substring line 0 i)) (setq i (1+ (string-search-char #\: line (1+ i)))) (setq uid (parse-integer line :start i :end (string-search-char #\: line i))) (when (and (not (= #\D (aref line 0))) (not (assq uid alist))) (push (list uid name ) alist)))))))) (defvar *filter-types* '("QFASL" "BIN" "XFASL" "FASL" "OFASL")) (defun tar-create-getfiles (spec) (if (atom spec) (labels ((rec (x) (do ((l (fs:directory-list x) (cdr l)) (r)(d)) ((null l) (nreconc r (mapcan #'rec (nreverse d)))) (cond ((not (caar l))) ((get (car l) :directory) (push (send (send (caar l) :pathname-as-directory) :new-pathname :name (send x :name) :type (send x :type) :version (send x :version)) d)) ((mem #'string-equal (send (caar l) :type) *filter-types*)) ('else (push (caar l) r)))))) (let ((p (fs:parse-pathname spec))) (rec (send p :new-pathname :name (or (send p :name) :wild) :type (or (send p :type) :wild) :version :newest)))) (apply #'append (mapcar #'tar-create-getfiles spec)))) (defun tar-create (stream spec) (let ((tbuf (make-string *tblock*)) (dirs-made nil)) (dolist (f (tar-create-getfiles spec)) (with-open-file (istream f) (let* ((truename (send istream :truename)) (dummy-name (send truename :new-pathname :host dummy-unix-host)) (nsize (or (get istream :length) (get istream :length-in-bytes)))) (when *verbose* (format t "~&~A ~D bytes~%" truename nsize)) (let ((uid (format nil "~O" (or (car (rass #'(lambda (i x) (string-equal i (car x))) (get istream :author) *uid-alist*)) 0))) (mtime (format nil "~O" (universal-time->unix-time (get istream :creation-date))))) (unless (mem #'equalp (send truename :directory) dirs-made) (push (send truename :directory) dirs-made) (encode-hblock tbuf (send dummy-name :directory-string) uid "0" mtime) (send stream :string-out tbuf)) (encode-hblock tbuf (send dummy-name :string-for-host) uid (format nil "~O" nsize) mtime) (send stream :string-out tbuf) (multiple-value-bind (nblocks left) (floor nsize *tblock*) (dotimes (j nblocks) (string-in istream tbuf) (unix->lispm-translate tbuf) (send stream :string-out tbuf)) (unless (zerop left) (fill tbuf 0) (send istream :string-in nil tbuf 0 left) (unix->lispm-translate tbuf) (send stream :string-out tbuf))))))) (fill tbuf 0) (send stream :string-out tbuf))) (defvar *null-string-1* (make-string 1)) (setf (aref *null-string-1* 0) #\0) (defun encode-hblock (string name uid size mtime) (fill string 0) (encode-hblock-item string 'name (if (and (not (zerop (length name))) (= (aref name 0) #//)) (substring name 1) name)) (encode-hblock-item string 'mode (if (and (not (zerop (length name))) (= (aref name (1- (length name))) #//)) "777" "666")) (encode-hblock-item string 'uid uid) (encode-hblock-item string 'gid "0") (encode-hblock-item string 'size size) (encode-hblock-item string 'mtime mtime) (encode-hblock-item string 'chksum " ") (encode-hblock-item string 'linkflag *null-string-1*) (encode-hblock-item string 'linkname "") (do ((sum 0 (+ sum (aref string j))) (j 0 (1+ j)) (n (length string))) ((= j n) (encode-hblock-item string 'chksum (format nil "~O" sum))))) (defun encode-hblock-item (into-string name from-string) (let ((c (assq name *hblock*))) (let ((start (nth 3 c)) (end (nth 4 c)) (n (length from-string))) ;; if from-string is too short then the rest is taken as 0 ;; which is correct for unix. (copy-array-portion from-string 0 n into-string start end))))