;;; -*- Mode:LISP; Package:USER; Base:10 -*- ;;; so that I can give correct information to Bob P about the ;;; layout of the first few blocks of the FILE partition. ;;; Info to be use by a C program to describe the partition. ;;; 6-Dec-85 11:20:51 -George Carrette ;;; structure of the filesystem band. ;;; 24-Bit integers are used quite a bit for block numbers, sizes and offsets. ;;; A number is noted with square brackets [X] and is 3 bytes unless noted. ;;; The numbers are read in a byte at a time, high-byte-first. ;;; Some things are strings terminated by the lispm character #o215. ;;; ;;; ;;; [VERSION][CHECK][SIZE][PUT-BASE][PUT-SIZE]{ROOT-DIRECTORY-MAP} ;;; ;;; A {MAP} is a description of what blocks make up a file. ;;; It is of the form [LENGTH(2 BYTES)][LOCATION0][SIZE0][LOCATION1][SIZE1] ... ;;; The LOCATION is in BLOCKS (1024 bytes) and the SIZE is in BITS. ;;; The PUT (Page Usage Table) is an array of 2-bit quantities ;;; 0 = free block ;;; 1 = reserved ;;; 2 = used ;;; 3 = bad ;;; A directory is a list of directory entries that describe files. ;;; ;;; ;;; ;;; [VERSION][BYTESIZE(1 BYTE)] ;;; ;;; [CREATION-DATE(4 bytes)] ;;; {MAP} ;;; [ATTRIBUTES(2 BYTES)] ;;; {PROPERTY-LIST} ;;; There are 7 file attribute bits that are meaningful: ;;; 0 :DONT-DELETE ;;; 1 :CLOSED ;;; 2 :DELETED ;;; 3 :DUMPED ;;; 4 :DONT-REAP ;;; 5 :CHARACTERS ;;; 6 :DIRECTORY ;;; The {PROPERTY-LIST} is ;;; [NUMBER-OF-ENTRIES(1 BYTE)]{STRING}{ENTRY} .... ;;; A {STRING} is [LENGTH(1 BYTE)] ;;; An {ENTRY} is [OPCODE(1 BYTE)]{optional-opcode-dependant} ;;; Opcodes are: ;;; 0 = false, no optional ;;; 1 = true, no optional ;;; 2 {STRING} keyword symbol ;;; 3 {STRING}{STRING} symbol name and package ;;; 4 [INTEGER] 3 bytes of course. ;;; 5 {STRING} a string ;;; 6 {READ} Must use lisp READ. ;;; 7 {READ} Must use lisp READ. ;;; 6 and 7 are rare. (defun describe-file-partition (partition &optional &key (unit 0)) (with-open-stream (s (make-partition-input-stream :partition-name partition :unit unit :byte-size 8)) (let ((version (read-next-bytes s 3)) (check (read-next-bytes s 3)) (size (read-next-bytes s 3)) (page-usage-table-base (read-next-bytes s 3)) (page-usage-table-size (read-next-bytes s 3))) (format t "~&Partition: ~S~%LM-FILE version: ~D (~:[in~;~]valid) Check: ~D (~:[in~;~]valid)~ ~%Size ~D blocks, Page Usage Table: Start ~D, length ~D blocks~%" (send s :truename) version (<= 3 version 4) check (zerop check) size page-usage-table-base page-usage-table-size) (let ((map-size (read-next-bytes s 2))) (format t "Root directory Map is ~D element~p long~%" map-size map-size) (dotimes (j map-size) (format t "Start: ~D (block), Length ~D (bits)~%" (read-next-bytes s 3) (read-next-bytes s 3)))) (describe-page-usage-table s page-usage-table-base page-usage-table-size)))) (defun describe-page-usage-table (s base size) (format t "Page Usage (. = free, @ = reserved, * = used, ? = bad)") (send s :set-pointer (* base 1024)) (let ((previous-line (make-string 64 :fill-pointer 0)) (current-line (make-string 64 :fill-pointer 0)) (previous-page -64) (current-page 0) (isize (length (format nil "~D" (* size 1024 4))))) (do ((j 0 (1+ j)) (n (* size 1024))) ((= j n) (or (zerop (fill-pointer current-line)) (format t "~%~VD~A ~A~%" isize current-page (if (= current-page (+ 64 previous-page)) ":" ">") current-line))) (let ((b (read-next-bytes s 1))) (dotimes (k 4) (array-push current-line (aref #(#\. #\@ #\* #\?) (ldb (byte 2 (* k 2)) b))))) (when (= 15 (mod j 16)) (when (not (string-equal previous-line current-line)) (format t "~%~VD~A ~A" isize current-page (if (= current-page (+ 64 previous-page)) ":" ">") current-line) (setq previous-line (prog1 current-line (setq current-line previous-line))) (setq previous-page current-page)) (setf (fill-pointer current-line) 0) (setf current-page (* (1+ j) 4)))))) (defun read-next-bytes (stream nbytes) ;; this means that numbers are stored hi-byte first. (do ((number 0 (+ (* number 256) (send stream :tyi))) (j 0 (1+ j))) ((= j nbytes) number))) (defun make-map-stream (map substream &aux stream (sofar 0) peekc) "Map is a list ((pointer byte-count) (pointer byte-count) ...)" (send substream :set-pointer (caar map)) (setq stream #'(lambda (op &optional arg1 &rest args) (si:selectq-with-which-operations op (:tyi (cond (peekc (prog1 peekc (setq peekc nil))) ((null map) (ferror nil "end of file on map stream")) ((not (= sofar (cadar map))) (incf sofar) (send substream :tyi)) ((null (setq map (cdr map))) nil) ('else (send substream :set-pointer (caar map)) (setq sofar 0) (send stream :tyi)))) (:untyi (setq peekc arg1)) (:substream substream) (t (stream-default-handler stream op arg1 args)))))) (defun get-map (s) (verbose "file map" (map #'(lambda (sublist) (setf (car sublist) (list (* (read-next-bytes s 3) 1024) (quotient (read-next-bytes s 3) 8)))) (make-list-for-map (read-next-bytes s 2))))) (defvar *verbose* t) (defun verbose (comment value) (if *verbose* (format t " ~A: ~S " comment value)) value) (defun make-list-for-map (n) (if (> n 100) (cerror "make it anyway" "unusually large file map: ~S" n)) (make-list n)) (defun get-line (s) (send s :line-in t)) (defun get-string (s) (let ((st (make-string (read-next-bytes s 1)))) (send s :string-in nil st) st)) (defun list-root-directory (partition &key (unit 0) recursive) (with-open-stream (s (make-partition-input-stream :partition-name partition :unit unit :byte-size 8)) (send s :set-pointer 15) (describe-directory-stream NIL (make-map-stream (get-map s) s) recursive))) (defun view-root-directory (partition &key (unit 0)) (with-open-stream (s (make-partition-input-stream :partition-name partition :unit unit :byte-size 8)) (send s :set-pointer 15) (stream-copy-until-eof (make-map-stream (get-map s) s) terminal-io))) (defun describe-directory-stream (DIR s recursive) (do () ((not (describe-directory-entry DIR s recursive))))) (defun describe-directory-entry (DIR s recursive &aux d a m) (cond ((not (send s :tyipeek)) (format t "~&End of directory~%") nil) ('else (format t "~&~{~A~^.~};~S " dir (setq d (get-line s))) (format t "~S " (get-line s)) (format t "~D " (read-next-bytes s 3)) (format t "(~D) " (read-next-bytes s 1)) (format t "~S " (get-line s)) (time:print-universal-time (read-next-bytes s 4)) (setq m (get-map s)) (format t " ~D byte~p" (apply '+ (mapcar 'cadr m)) (apply '+ (mapcar 'cadr m))) (setq a (read-next-bytes s 2)) (dolist (x '((0 :DONT-DELETE) (1 :CLOSED) (2 :DELETED) (3 :DUMPED) (4 :DONT-REAP) (5 :CHARACTERS) (6 :DIRECTORY))) (if (not (zerop (ldb (byte 1 (car x)) a))) (format t " ~S" (cadr x)))) (let ((nplist (read-next-bytes s 1))) (cond ((zerop nplist) (terpri)) ('else (terpri) (dotimes (j nplist) (princ (get-string s)) (princ ": ") (ecase (read-next-bytes s 1) (0 (princ "NIL")) (1 (princ "T")) (2 (princ (get-string s))) (3 (format t "~A(~A)" (get-string s) (get-string s))) (4 (format t "~D" (read-next-bytes s 3))) (5 (princ (get-string s))) ((6 7) (format t "(READ) ~S" (read s)))) (terpri))))) (when (and recursive (cond ((eq recursive t)) ((stringp recursive) (string-equal recursive d)) ((consp recursive) (string-equal (car recursive) d))) m (not (zerop (ldb (byte 1 6) a)))) (let ((p (send (send s :substream) :read-pointer))) (format t "~&Subdirectory: ~S~%" d) (describe-directory-stream (append dir (list d)) (make-map-stream m (send s :substream)) (if (consp recursive) (or (cdr recursive) t) t)) (send (send s :substream) :set-pointer p))) t))) (defun make-partition-input-stream (&key partition-name (unit 0) (byte-size 8)) ;; a non-flavorized version of si:make-partition-input-stream (let (rqb u winp) (unwind-protect (progn (setq u (si:decode-unit-argument unit "random reading")) (multiple-value-bind (start length nil truename) (si:find-disk-partition-for-read partition-name nil u) (setq rqb (si:get-disk-rqb 1)) (prog1 (make-partition-input-stream-1 truename rqb u start length 0 (quotient (* 1024 8) byte-size) (ecase byte-size (16 (si:rqb-buffer rqb)) (8 (si:rqb-8-bit-buffer rqb)))) (setq winp t)))) (when (not winp) (and rqb (si:return-disk-rqb rqb)) (and u (si:dispose-of-unit u)))))) (defun make-partition-input-stream-1 (nm rqb unit start n pointer block-size buffer &aux stream) (setq stream #'(lambda (op &optional arg1 &rest args) (si:selectq-with-which-operations op (:tyi (cond ((null pointer) (ferror nil "end of file on disk partition")) ((= pointer (* block-size n)) (setq pointer nil)) ('else (if (zerop (mod pointer block-size)) (send stream :set-pointer pointer)) (prog1 (aref buffer (mod pointer block-size)) (incf pointer))))) (:set-pointer (check-arg arg1 (and (<= 0 arg1) (< arg1 (* block-size n))) "a byte pointer inside the disk partition") (setq pointer arg1) (si:disk-read rqb unit (+ start (floor pointer block-size)))) (:read-pointer pointer) (:truename nm) (:close (si:return-disk-rqb (prog1 rqb (setq rqb nil))) (si:dispose-of-unit unit)) (t (stream-default-handler stream op arg1 args))))))