;;; -*- Mode:LISP; Package:(UDISK GLOBAL); Base:10; Readtable:t -*- #| Copyright LISP Machine, Inc. 1985, 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. *** ********************************************************* ********************************************************* This answers the question: "How do I access the disk at a lower level of abstraction than the file system or virtual memory?" 2/25/85 15:21:15 -George Carrette Usage: First, choose a disk partition from: (print-disk-label) that is not being used for anything else such as FILE (filesystem), LMC (microcode), LOD (lisp saved environments), PAGE (virual memory support), METER (the METER:TEST functions). The METER partition is a good one to use for testing purpose if you are not otherwise using it. If a free partition does not exist then you will have to use SI:EDIT-DISK-LABEL to create one, by (for example) changing the name of an used LOD partition to USER, or MINE, etc. Let's say we call this partition "USER". Then: (initialize-disk-partition "USER" "For my own database") You need to do the above only once. Then: (setq p (mount-disk-partition "USER" :ACCESS :WRITE)) Then: (read-disk-block p 3) or: (write-disk-block p 3 "a string") or: (setf (read-disk-block p 3) "a string") More abstract, presumably safer and easier to use routines for hacking "the disk" at a lower level than the file system allows. This may make it easier for people to implement their own file systems and databases. Initialization: This doesn't do anything but set up the name and comment and perhaps take optional arguments for zeroing out a partition, testing for bad blocks etc. Maybe reserve the first block of the partition for useful information such to be maintained by the mount/dismount functions about who initialized and mounted ande when etc. If software testing for bad blocks is implemented then keep a bitmap of bad-block information which the READ-BLOCK and WRITE-BLOCK functions can check to prevent people from losing by using bad blocks. We could probably do well to abstract further: Each block on disk with a type code, a reference count. Have a bit map of block usage, reserved and free. The overhead for this sort of thing is minimal Use a type borrowed from lisp implementation experience, a BIBOP like in PDP-10 maclisp allows raw blocks to be very raw indeed. We might also want to implement a process-lock on disk partitions. Note well: There is no INTERPROCESS LOCK at this level. The user should handle this himself, by handshaking via shared-memory read/write for example. Stream Level Interface: Instead of having just read/write-block you might want to model the partition as a continguous stream of bytes. Be able to using :TYI, :TYO, :SET-POINTER, and :READ-POINTER. And of course, :FORCE-OUTPUT when needed. For this use MAKE-DISK-PARTITION-STREAM, called on a disk partition. There can be multiple streams pointing to the same partition, just be careful that they arent pointing to the same section of disk at the same time. TO DO: implement multiple streams with interlocking. |# (defun canonicalize-disk-partition-name (name) (check-type name (or string symbol)) (intern (string-upcase name) "")) (defstruct (disk-partition (conc-name disk-partition.) :named (:print "#" (disk-partition.name disk-partition) (disk-partition.mounted disk-partition) (disk-partition.length disk-partition))) name unit start length mounted ; NIL, :READ or :WRITE rqb transfer-size lock wired? max-block ) (defun initialize-disk-partition (partition-name partition-comment &optional &key (unit 0) (zero? nil) (check? nil)) (check-type partition-name (or string symbol)) (check-type partition-comment string) "Initialize a disk partition for later use by the function MOUNT-DISK-PARTITION which is in turn a prequisite for using READ-DISK-BLOCK and WRITE-DISK-BLOCK. Optional arguments specify the zeroing of blocks and the checking for bad-blocks." (let ((p (instantiate-disk-partition partition-name unit))) (unwind-protect (progn (mount-disk-partition p :access :write :wired? t) (sys:update-partition-comment (disk-partition.name p) partition-comment unit) (if check? (check-disk-partition p)) (if zero? (zero-disk-partition p))) (dismount-disk-partition p)) p)) (defun return-disk-partition-stores (x) (when (disk-partition.rqb x) (si:unwire-disk-rqb (disk-partition.rqb x)) (si:return-disk-rqb (prog1 (disk-partition.rqb x) (setf (disk-partition.rqb x) nil))))) (defvar *instantiated-disk-partitions* nil) (add-initialization "dismount user disk partitions" '(mapc #'dismount-disk-partition *instantiated-disk-partitions*) '(:before-cold)) (defun instantiate-disk-partition (partition-name &optional (unit 0)) (let ((name (canonicalize-disk-partition-name partition-name))) (cond ((cadr (assq unit (get name :disk-partitions)))) ('else (unless (SYS:FIND-DISK-PARTITION NAME NIL UNIT) (ferror nil "No disk partition named: ~A" name)) (let ((p (make-disk-partition name name unit unit))) (push p *instantiated-disk-partitions*) (push (list unit p) (get name :disk-partitions)) p))))) (defun mount-disk-partition (x &optional &key (access :read) (transfer-size-in-bytes 1024) (wired? t) (ask-about-write t) (unit 0)) (check-arg x (typep x '(or disk-partition string)) "An instance or the name of a disk partition") (if (stringp x) (setq x (instantiate-disk-partition x unit))) (check-arg access (memq access '(:read :write)) "either :READ or :WRITE") (if (disk-partition.mounted x) (dismount-disk-partition x)) (return-disk-partition-stores x) (if (not (numberp (disk-partition.unit x))) (setq wired? nil)) (multiple-value-bind (start size) (funcall (CASE ACCESS (:READ #'si:find-disk-partition-for-read) (:WRITE (IF ASK-ABOUT-WRITE #'SI:FIND-DISK-PARTITION-FOR-WRITE #'SI:FIND-DISK-PARTITION))) (disk-partition.name x) nil (disk-partition.unit x)) (cond ((null start) (ferror nil "partition not found or access not granted")) ((not (zerop (mod transfer-size-in-bytes (* 4 si:page-size)))) (ferror nil "transfer size ~D not a multiple of ~D" transfer-size-in-bytes (* 4 si:page-size))) ((not (zerop (mod size (floor transfer-size-in-bytes (* 4 si:page-size))))) (ferror nil "partition not the right size for transfers of ~D bytes" transfer-size-in-bytes)) ('else (setf (disk-partition.rqb x) (si:get-disk-rqb (ceiling transfer-size-in-bytes (* 4 si:page-size)))) (cond (wired? (si:wire-disk-rqb (disk-partition.rqb x)) (setf (disk-partition.wired? x) t)) ('else (setf (disk-partition.wired? x) nil))) (setf (disk-partition.start x) start) (setf (disk-partition.length x) size) (setf (disk-partition.max-block x) (- size (si:rqb-npages (disk-partition.rqb x)))) (setf (disk-partition.transfer-size x) transfer-size-in-bytes) (setf (disk-partition.mounted x) (cadr (assq access '((:read (:read)) (:write (:read :write)))))) x)))) (defun dismount-disk-partition (x) (check-type x disk-partition) (cond ((disk-partition.mounted x) (setf (disk-partition.mounted x) nil) (return-disk-partition-stores x) x) ('else x))) (defmacro check-partition-arg (arg access) `(progn (check-type ,arg disk-partition) (or (memq ,access (disk-partition.mounted ,arg)) (ferror nil "~S doesnt support ~A access" ,arg ,access)))) (defmacro check-block-arg (part block) `(check-arg ,block (and (fixp ,block) (not (< ,block 0)) (not (> ,block (disk-partition.max-block ,part)))) "an integer fitting in the disk partition size")) ;; READ and WRITE: (defun read-disk-block (part block &optional into) "Reads block BLOCK from the partition and copies the data into string INTO" (check-partition-arg part :read) (check-block-arg part block) (let ((rqb (disk-partition.rqb part)) (temp)) (cond ((not (disk-partition.wired? part)) (si:disk-read rqb (disk-partition.unit part) (+ (disk-partition.start part) block))) ((and (setq temp (si:%page-status rqb)) (= (ldb si:%%PHT1-SWAP-STATUS-CODE temp) si:%PHT-SWAP-STATUS-WIRED)) (si:disk-read-wired rqb (disk-partition.unit part) (+ (disk-partition.start part) block))) ('else (ferror nil "internal inconsistency disk rqb not wired when expected"))) (unless into (setq into (make-array (disk-partition.transfer-size part) :type 'art-string))) (copy-array-contents (sys:rqb-8-bit-buffer rqb) into)) into) (defsetf read-disk-block write-disk-block) (defun write-disk-block (part block value) "Write the string VALUE into the block BLOCK inside the partition PART" (check-partition-arg part :write) (check-block-arg part block) (check-type value string) (let ((rqb (disk-partition.rqb part)) (temp)) (copy-array-contents value (sys:rqb-8-bit-buffer rqb)) (cond ((not (disk-partition.wired? part)) (si:disk-write rqb (disk-partition.unit part) (+ (disk-partition.start part) block))) ((and (setq temp (si:%page-status rqb)) (= (ldb si:%%PHT1-SWAP-STATUS-CODE temp) si:%PHT-SWAP-STATUS-WIRED)) (si:disk-write-wired rqb (disk-partition.unit part) (+ (disk-partition.start part) block))) ('else (ferror nil "internal inconsistency disk rqb not wired when expected"))))) ;;; maintenence and allocation: (defun zero-disk-partition (x) (check-partition-arg x :write) (describe x) (let* ((length (disk-partition.length x)) (blockn (floor (disk-partition.transfer-size x) (* 4 si:page-size))) (n-writes (floor length blockn)) (peek-every (ceiling n-writes 500))) (format t "Must do ~D writes. Watch for ~D row~p of dots. One dot = ~D write~p~%" n-writes (ceiling n-writes (* peek-every 50)) (ceiling n-writes (* peek-every 50)) peek-every peek-every) (fill (si:rqb-buffer (disk-partition.rqb x)) 0) (dotimes (j n-writes) (write-disk-block x (* j blockn) "") (if (zerop (mod j peek-every)) (princ ".")) (if (zerop (mod (1+ j) (* peek-every 50))) (terpri))))) (defun check-disk-partition (x) (format t "~&Checking of ~S is unimplemented~%" x)) ;;; tests and examples: (defun a-simple-test (x) (do ((s)(n 0 (1+ n))) ((null (setq s (prompt-and-read ':string-or-nil "Line #~D>" n))) (do ((j 0 (1+ j)) (st nil)) ((= j n)) (setq st (read-disk-block x j st)) (send terminal-io ':string-out st 0 (string-search-char 0 st)) (terpri terminal-io))) (write-disk-block x n s))) (defflavor stream-default-handler-mixin ;; this should be a built-in system feature () ()) (defmethod (stream-default-handler-mixin :lossage) (op) (ferror :unclaimed-message "The stream operation ~S is not supported by ~S" op self)) (defmethod (stream-default-handler-mixin :tyipeek) () (let ((c (send self :tyi))) (when c (send self :untyi c) c))) (defmethod (stream-default-handler-mixin :any-tyi) () (send self :tyi)) (defmethod (stream-default-handler-mixin :tyi-no-hang) () (send self :tyi)) (defmethod (stream-default-handler-mixin :any-tyi-no-hang) () (send self :any-tyi)) (defmethod (stream-default-handler-mixin :read-char) () (if (not (send self :characters)) (send self :lossage :read-char)) (let ((c (send self :tyi))) (if (fixnump c) (int-char c) c))) (defmethod (stream-default-handler-mixin :any-read-char) () (send self :read-char)) (defmethod (stream-default-handler-mixin :read-char-no-hang) () (send self :read-char)) (defmethod (stream-default-handler-mixin :any-read-char-no-hang) () (send self :any-read-char)) (defmethod (stream-default-handler-mixin :read-byte) () (send self :tyi)) (defmethod (stream-default-handler-mixin :unread-char) (c) (if (not (send self :characters)) (send self :lossage :unread-char)) (send self :untyi (if (characterp c) (char-int c) c))) (defmethod (stream-default-handler-mixin :write-char) (c) (if (not (send self :characters)) (send self :lossage :write-char)) (send self :tyo (if (characterp c) (char-int c) c))) (defmethod (stream-default-handler-mixin :write-byte) (c) (send self :tyo c)) (defmethod (stream-default-handler-mixin :clear-output) () ()) (defmethod (stream-default-handler-mixin :clear-input) () ()) (defmethod (stream-default-handler-mixin :force-output) () ()) (defmethod (stream-default-handler-mixin :finish) () ()) (defmethod (stream-default-handler-mixin :close) () ()) (defmethod (stream-default-handler-mixin :eof) () ()) (defmethod (stream-default-handler-mixin :fresh-line) () (send self :tyo #\newline)) (defmethod (stream-default-handler-mixin :string-out) (string &optional start end) (do ((j (or start 0) (1+ j)) (n (or end (length string)))) ((= j n)) (send self :tyo (aref string j)))) (defmethod (stream-default-handler-mixin :line-out) (string &optional start end) (send self :string-out string start end) (send self :tyo #\newline)) (defmethod (stream-default-handler-mixin :line-in) (&optional leader-length) (let ((buf (make-string #o100 :leader-length (if (numberp leader-length) leader-length 1)))) (setf (fill-pointer buf) 0) (values buf (do ((tem (send self :tyi) (send self :tyi))) ((or (null tem) (eq tem (char-int #/Newline)) (eq tem (char-int #/End))) (adjust-array-size buf (array-active-length buf)) (null tem)) (vector-push-extend (int-char tem) buf))))) (defmethod (stream-default-handler-mixin :string-in) (eof string &optional start end) (loop with start = (or start 0) and end = (or end (array-length string)) while (< start end) as ch = (send self :tyi) while ch do (aset ch string (prog1 start (incf start))) finally (and (array-has-leader-p string) (store-array-leader start string 0)) (and (null ch) eof (ferror 'si:end-of-file-1 "End of file on ~S." self)) (return (values start (null ch))))) (defmethod (stream-default-handler-mixin :string-line-in) (eof string &optional start end) (loop with start = (or start 0) and end = (or end (array-length string)) while (< start end) as ch = (send self :tyi) while (and ch (neq ch (char-int #/Newline))) do (setf (char string (prog1 start (incf start))) (int-char ch)) finally (and (array-has-leader-p string) (setf (fill-pointer string) start)) (and (null ch) eof (ferror 'si:end-of-file-1 "End of file on ~S." self)) (return (values start (null ch) (neq ch #/Return))))) (defmethod (stream-default-handler-mixin :characters) () t) (defmethod (stream-default-handler-mixin :interactive) () nil) (defmethod (stream-default-handler-mixin :element-type) () (if (send self :characters) 'character (let ((value (send self :send-if-handles :byte-size))) (if value `(unsigned-byte ,value) 'fixnum)))) (defmethod (stream-default-handler-mixin :direction) () (let ((ops (send self :which-operations))) (if (memq :tyi ops) (if (memq :tyo ops) :bidirectional :input) (if (memq :tyo ops) :output nil)))) ;;; A Stream that could be used as a reliable log-file. (defflavor disk-partition-stream (partition buffer pointer block index size need-force-output) (stream-default-handler-mixin) :initable-instance-variables) (defun make-disk-partition-stream (x) (check-type x disk-partition) (make-instance 'disk-partition-stream :buffer (make-string (disk-partition.transfer-size x)) :partition x :pointer 0 :block -1 :size (* (disk-partition.length x) (* 4 si:page-size)) :need-force-output nil)) (defmethod (disk-partition-stream :after :init) (plist) plist (send self :setup-pointer)) (defmethod (disk-partition-stream :print-self) (stream level flag) level flag (format stream "#" pointer partition)) (defmethod (disk-partition-stream :read-pointer) () pointer) (defmethod (disk-partition-stream :set-pointer) (n) (when (and need-force-output (not (= block (* (floor n (disk-partition.transfer-size partition)) (quotient (disk-partition.transfer-size partition) (* 4 si:page-size))) block))) (send self :force-output)) (setq pointer n) (send self :setup-pointer)) (defmethod (disk-partition-stream :setup-pointer) () (multiple-value-bind (quotient remainder) (floor pointer (disk-partition.transfer-size partition)) (let ((new-block (* quotient (quotient (disk-partition.transfer-size partition) (* 4 si:page-size))))) (when (not (= new-block block)) (setq block new-block) (read-disk-block partition block buffer))) (setq index remainder))) (defmethod (disk-partition-stream :force-output) () (when need-force-output (write-disk-block partition block buffer) (setq need-force-output nil))) (defmethod (disk-partition-stream :tyi) () (cond ((= pointer size) ()) ((= index (length buffer)) (send self :setup-pointer) (prog1 (aref buffer index) (incf index) (incf pointer))) ('else (prog1 (aref buffer index) (incf index) (incf pointer))))) (defmethod (disk-partition-stream :untyi) (c) (if (zerop index) (ferror nil "UNTYI not after TYI")) (decf pointer) (decf index) (unless (= c (aref buffer index)) (ferror nil "UNTYI of character different from that TYI'd"))) (defmethod (disk-partition-stream :read-input-buffer) () (cond ((= pointer size) ()) ((= index (length buffer)) (send self :setup-pointer) (values buffer index (length buffer))) ('else (values buffer index (length buffer))))) (defmethod (disk-partition-stream :advance-input-buffer) () (let ((amount (- (length buffer) index))) (incf index amount) (incf pointer amount))) (defmethod (disk-partition-stream :tyo) (c) (when (= index (length buffer)) (send self :setup-pointer)) (setf (aref buffer index) c) (setq need-force-output t) (when (= (1+ index) (length buffer)) (send self :force-output)) (incf index) (incf pointer)) (defmethod (disk-partition-stream :close) (&optional abortp) (if (and (not abortp) (memq :write (disk-partition.mounted partition))) (send self :force-output)) (setq buffer nil)) ;; example database. (DEFVAR *DOC-UNIT* 0) (DEFVAR *DOC-PARTITION* "METR") (DEFVAR *DOC-STREAM* NIL) (DEFVAR *DOC-ALIST* NIL) (DEFUN DOC (F) (WHEN (STRINGP *DOC-PARTITION*) (FORMAT T "~&Opening up doc database...") (SETQ *DOC-PARTITION* (INSTANTIATE-DISK-PARTITION *DOC-PARTITION* *DOC-UNIT*)) (MOUNT-DISK-PARTITION *DOC-PARTITION*) (SETQ *DOC-STREAM* (MAKE-DISK-PARTITION-STREAM *DOC-PARTITION*)) (DO ((J 0 (1+ J)) (N 0 (+ N (* (EXPT 256 J) (SEND *DOC-STREAM* :TYI))))) ((= J 8) (SEND *DOC-STREAM* :SET-POINTER N) (SETQ *DOC-ALIST* (LET ((IBASE 10) (*READTABLE* (SI:FIND-READTABLE-NAMED "ZL")) (*PACKAGE* (FIND-PACKAGE "UDISK"))) (READ *DOC-STREAM*))))) (FORMAT T " done.~%")) (LET ((DOC (ASS #'STRING-EQUAL F *DOC-ALIST*))) (COND ((NOT DOC) (format T "~&~S is undocumented~%" F)) ('ELSE (format t "~&Documentation for ~S~%" F) (SEND *DOC-STREAM* :SET-POINTER (CADR DOC)) (STREAM-COPY-N-BYTES (CADDR DOC) *DOC-STREAM* STANDARD-OUTPUT) (TERPRI STANDARD-OUTPUT))))) (DEFUN STREAM-COPY-N-BYTES (N FROM-STREAM TO-STREAM) (COND ((AND (MEMQ :READ-INPUT-BUFFER (SEND FROM-STREAM :WHICH-OPERATIONS)) (MEMQ :STRING-OUT (SEND TO-STREAM :WHICH-OPERATIONS))) (DO ((BUF) (OFFSET) (LIMIT) (J 0)) (()) (MULTIPLE-VALUE (BUF OFFSET LIMIT) (SEND FROM-STREAM :READ-INPUT-BUFFER)) (COND ((NULL BUF) (RETURN J))) (LET ((AMOUNT (MIN (- N J) (- LIMIT OFFSET)))) (SEND TO-STREAM :STRING-OUT BUF OFFSET (+ OFFSET AMOUNT)) (INCF J AMOUNT)) (COND ((= J N) (RETURN J))) (SEND FROM-STREAM :ADVANCE-INPUT-BUFFER))) ('ELSE (DO ((J 0 (1+ J)) (C)) ((NOT (SETQ C (SEND FROM-STREAM :TYI))) J) (SEND TO-STREAM :TYO C))))) (DEFUN DUMP-DOC (&OPTIONAL (PKG "GLOBAL")) (LET ((P (MOUNT-DISK-PARTITION *DOC-PARTITION* :ACCESS :WRITE :UNIT *DOC-UNIT*))) (WITH-OPEN-STREAM (S (MAKE-DISK-PARTITION-STREAM P)) (LET (DS) (FORMAT T "~&Collecting documentation in package ~S" pkg) (MAPATOMS #'(LAMBDA (SYM) (IF (DOCUMENTATION SYM) (PUSH (LIST SYM (DOCUMENTATION SYM) NIL) DS))) PKG NIL) (format t " ~D items found, writing strings..." (length ds)) (SEND S :SET-POINTER 8) (DOLIST (D DS) (LET ((P (SEND S :READ-POINTER)) (N (LENGTH (CADR D)))) (SEND S :STRING-OUT (CADR D)) (SETF (CADR D) P) (SETF (CADDR D) N))) (format t " writing alist...") (LET ((P (SEND S :READ-POINTER))) (LET ((BASE 10) (*READTABLE* (SI:FIND-READTABLE-NAMED "ZL")) (*PACKAGE* (FIND-PACKAGE "UDISK")) (*print-pretty* nil)) (PRINT DS S)) (SEND S :SET-POINTER 0) (DO ((J 0 (1+ J)) (N P (QUOTIENT N 256))) ((= J 8)) (SEND S :TYO (REMAINDER N 256)))) (format t " done.~%")))))