;;; -*- Mode:LISP; Package:TAPE; Readtable:CL; Base:10 -*-
;;
;; Copyright LISP Machine, Inc. 1986
;;   See filename "Copyright" for
;; licensing and release information.
;;;
;;; Error handling support for tape software
;;;
;;; -dg 10/04/85
;;;

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; Base flavor for all tape-related errors
;;;

(defflavor tape-error () (error)
  :abstract-flavor)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; Hardware errors - Errors that are internal hardware errors and cannot
;;; be reasonably handled by other software (fatal hardware lossage)
;;;

(defflavor hardware-error (device-type error-code error-message)
	   (tape-error)
  :gettable-instance-variables
  :initable-instance-variables
  (:required-init-keywords :device-type :error-code :error-message))

(defmethod (hardware-error :report) (stream)
  (format stream "Tape hardware error: ~A"
	  error-message))

(compile-flavor-methods hardware-error)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; Driver errors - Internal errors in the driver software.
;;; This is for internal error checking, which for the most part
;;; probably won't be common, but those who like to setup internal
;;; error checking, will have this available (rather than "(ferror nil ...)")
;;; It is also used to catch internal conditions.

(defflavor driver-error (device-type error-code error-message)
	   (tape-error)
  :gettable-instance-variables
  :initable-instance-variables
  (:required-init-keywords :device-type :error-code :error-message))


(defmethod (driver-error :report) (stream)
  (format stream "Tape device driver error: ~A"
	  error-message))

(compile-flavor-methods driver-error)
  
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; Device Error - Typical (sometimes expected) errors that the device may
;;; encounter, such as the physical end of the tape, device not on ready, etc.
;;;
;;;

(defflavor device-error () (tape-error)
  :abstract-flavor)

;;; Device not found

(defflavor device-not-found () (device-error)
  (:required-init-keywords :format-string))

;;; Device unavailable 

(defflavor device-unavailable (device-type current-owner)
	   (device-error)
  :gettable-instance-variables
  :initable-instance-variables
  (:required-init-keywords :device-type :current-owner))

(defmethod (device-unavailable :report) (stream)
  (etypecase current-owner
    (integer
     (format stream "The ~A device is currently being used by slot #d~d"
	     device-type current-owner))
    (cons
     (format stream "The ~a device is currently being used by the device object: ~S~%~
	             ~9Tin the process: ~S"
	     device-type
	     (cdr current-owner)
	     (car current-owner)))))

(defmethod (device-unavailable :case :proceed-asking-user :steal-device-internally)
	   (cont read-func)
  "Steal the device from another process.  *** Be careful ***"
  read-func
  (funcall cont :steal-device-internally))

(defmethod (device-unavailable :case :proceed-asking-user :steal-device-from-other-processor)
	   (cont read-func)
  "Steal the device from another processor."
  read-func
  (funcall cont :steal-device-from-other-processor))

(defmethod (device-unavailable :case :proceed-asking-user :wait-for-device-free)
	   (cont read-func)
  "Wait for the device to become free, then lock it."
  read-func
  (funcall cont :wait-for-device-free))

(compile-flavor-methods device-unavailable)

;;; Wait timeout

(defflavor wait-timeout (device-type unit seconds-waited wait-string)
	   (device-error)
  :gettable-instance-variables
  :initable-instance-variables
  (:required-init-keywords :device-type :seconds-waited :wait-string :unit))

(defmethod (wait-timeout :report) (stream)
  (format stream "The ~a device (unit ~D) timed out after ~A ~A"
	  device-type
	  unit
	  (time:print-interval-or-never seconds-waited nil)
	  wait-string))

(compile-flavor-methods wait-timeout)
  
;;; Bad tape

(defflavor bad-tape (device-type unit)
	   (device-error)
  :gettable-instance-variables
  :initable-instance-variables
  (:required-init-keywords :device-type :unit))

(defmethod (bad-tape :report) (stream)
  (format stream "A hard error has been found on the media on unit ~D the ~A device."
	  device-type unit))

(defmethod (bad-tape :case :proceed-asking-user :replace-bad-tape)
	   (cont read-func)
  "Replace the tape and start again from some point."
  read-func
  (funcall cont :replace-bad-tape))

(compile-flavor-methods bad-tape)
 
;;; Blank tape

(defflavor blank-tape (device-type unit)
	   (device-error)
  :gettable-instance-variables
  :initable-instance-variables
  (:required-init-keywords :device-type))

(defmethod (blank-tape :report) (stream)
  (format stream "The tape on ~A device (unit 0) seems to be blank."
	  device-type unit))

(compile-flavor-methods blank-tape)

;;; Physical end of tape

(defflavor physical-end-of-tape (device-type unit data-transferred)
	   (device-error)
  :gettable-instance-variables
  :initable-instance-variables
  (:required-init-keywords :device-type :unit :data-transferred))

(defmethod (physical-end-of-tape :report) (stream)
  (format stream "Physical end of tape on unit ~d of ~A device"
	  unit device-type))

(compile-flavor-methods physical-end-of-tape)

;;; Physical beginning of tape

(defflavor physical-beginning-of-tape (device-type unit data-transferred)
	   (device-error)
  :gettable-instance-variables
  :initable-instance-variables
  (:required-init-keywords :device-type :unit :data-transferred))

(defmethod (physical-beginning-of-tape :report) (stream)
  (format stream "Physical beginning of tape on unit ~d of ~A device"
	  unit device-type))

(compile-flavor-methods physical-beginning-of-tape)

;;; Filemark encountered during read

(defflavor filemark-encountered (device-type unit data-transferred)
	   (device-error)
  :gettable-instance-variables
  :initable-instance-variables
  (:required-init-keywords :device-type :unit :data-transferred))

(defmethod (filemark-encountered :report) (stream)
  (format stream "Filemark encountered during read on unit ~D of the ~A device"
	  unit device-type))

(compile-flavor-methods filemark-encountered)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; Format errors - Errors in the format of information on the tape with repect
;;; to the specific format standard (bad file headers, etc)
;;;

(defflavor format-error () (tape-error)
  :abstract-flavor)

;;; Bad tape header

(defflavor bad-tape-header (format-type header)
	   (format-error)
  :gettable-instance-variables
  :initable-instance-variables
  (:required-init-keywords :format-type :header))

(defmethod (bad-tape-header :report) (stream)
  (format stream "Bad data in tape header for ~A format." format-type))

(compile-flavor-methods bad-tape-header)

;;; Bad file headers

(defflavor bad-file-header (format-type header)
	   (format-error)
  :gettable-instance-variables
  :initable-instance-variables
  (:required-init-keywords :format-type :header))

(defmethod (bad-file-header :report) (stream)
  (format stream "Bad data in file header for ~A format." format-type))

(compile-flavor-methods bad-file-header)

;;; Logical end of tape

(defflavor logical-end-of-tape (device-object)
	   (format-error)
  :gettable-instance-variables
  :initable-instance-variables
  (:required-init-keywords :device-object))

(defmethod (logical-end-of-tape :report) (stream)
  (format stream "Logical end of tape on ~S" device-object))

(compile-flavor-methods logical-end-of-tape)

;;; End of tape writing header

(defflavor end-of-tape-writing-header (file-plist device)
	   (format-error)
  :gettable-instance-variables
  :initable-instance-variables
  (:required-init-keywords :file-plist))

(defmethod (end-of-tape-writing-header  :report) (stream)
  (format stream "End of tape encountered while writing header for file: ~A~&~
                  ~9Ton device ~A"
	  (car file-plist)
	  device))

(compile-flavor-methods end-of-tape-writing-header)

;;; End of tape reading header

(defflavor end-of-tape-reading-header (device)
	   (format-error)
  :gettable-instance-variables
  :initable-instance-variables
  (:required-init-keywords :device))

(defmethod (end-of-tape-reading-header :report) (stream)
  (format stream "End of tape encountered while reading header on device: ~S"
	  device))

(compile-flavor-methods end-of-tape-reading-header)
	  
;;; End of tape writing file

(defflavor end-of-tape-writing-file (file-plist device bytes-transferred)
	   (format-error)
  :gettable-instance-variables
  :initable-instance-variables
  (:required-init-keywords :file-plist :device :bytes-transferred))

(defmethod (end-of-tape-writing-file :report) (stream)
  (format stream "End of tape writing file ~A on device: ~S"
	  (car file-plist)
	  device))

(compile-flavor-methods end-of-tape-writing-file)

;;; End of tape reading file

(defflavor end-of-tape-reading-file (file-plist device bytes-transferred)
	   (format-error)
  :gettable-instance-variables
  :initable-instance-variables
  (:required-init-keywords :file-plist :device :bytes-transferred))
	   
(defmethod (end-of-tape-reading-file :report) (stream)
  (format stream "End of tape reading file ~A on device: ~S"
	  (car file-plist)
	  device))

(compile-flavor-methods end-of-tape-reading-file)

(defflavor compare-error (source-file file-plist) (format-error)
  :gettable-instance-variables
  :initable-instance-variables
  (:required-init-keywords :source-file :file-plist))

(defmethod (compare-error :report) (stream)
  (format stream "File comparison error for file: \"~A\"" source-file))

(compile-flavor-methods compare-error)

(defflavor compare-source-not-found (source-file) (format-error)
  :gettable-instance-variables
  :initable-instance-variables
  (:required-init-keywords :source-file))

(defmethod (compare-source-not-found :report) (stream)
  (format stream "File \"~A\" not found for comparison." source-file))

(compile-flavor-methods compare-source-not-found)

(defflavor compare-source-changed (source-plist file-plist) (format-error)
  :gettable-instance-variables
  :initable-instance-variables
  (:required-init-keywords :source-plist :file-plist))

(defmethod (compare-source-changed :source-file) ()
  (car source-plist))

(defmethod (compare-source-changed :report) (stream)
  (format stream
	  "File \"~A\" for comparing seems to have changed since writing."
	  (send self :source-file)))

(compile-flavor-methods compare-source-changed)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; User error - Errors that the user can correct and retry, or that the user
;;; must have caused by not following the conventions.
;;; (i.e. tape not mounted, :read-array arguments specify too much data for the 
;;;       array, etc.)
;;;

(defflavor user-error () (tape-error)
  :abstract-flavor)

;;; Tape write protected

(defflavor write-protected (device-type unit)
	   (user-error)
  :gettable-instance-variables
  :initable-instance-variables
  (:required-init-keywords :device-type :unit))

(defmethod (write-protected :report) (stream)
  (format stream "The tape in unit ~D of the ~A device is write protected."
	  unit device-type))

(compile-flavor-methods write-protected)

;;; Tape not ready

(defflavor tape-not-ready (device-type unit)
	   (user-error)
  :gettable-instance-variables
  :initable-instance-variables
  (:required-init-keywords :device-type :unit))

(defmethod (tape-not-ready :report) (stream)
  (format stream "Unit ~D of the ~A device is not ready."
	  unit device-type))

(compile-flavor-methods tape-not-ready)

(defflavor unknown-format (device unit header-string)
	   (user-error)
  :gettable-instance-variables
  :initable-instance-variables
  (:required-init-keywords :device :unit :header-string))

(defmethod (unknown-format :report) (stream)
  (format stream "Tape on unit ~d of device ~S is of unknown format."
	  unit device))

(compile-flavor-methods unknown-format)

(defflavor file-stream-exists (format-object file-stream)
	   (user-error)
  :gettable-instance-variables
  :initable-instance-variables
  (:required-init-keywords :format-object :file-stream))

(defmethod (file-stream-exists :report) (report-stream)
  (format report-stream 
	  "A file stream is currenly open for format: ~S" format-object))

(compile-flavor-methods file-stream-exists)

(defflavor no-such-partition (partition host disk-unit)
	   (user-error)
  :gettable-instance-variables
  :initable-instance-variables
  (:required-init-keywords :partition :disk-unit :host))

(defmethod (no-such-partition :report) (report-stream)
  (format report-stream 
	  "There is no partition named ~A on unit ~D of host ~A"
	  partition
	  disk-unit
	  host))

(compile-flavor-methods no-such-partition)

(defflavor invalid-option (object option value)
	   (user-error)
  :gettable-instance-variables
  :initable-instance-variables
  (:required-init-keywords :option :value :object))

(defmethod (invalid-option :report) (stream)
  (format stream "Invalid option ~A (with value ~S) for object ~S"
	  option value object))

(compile-flavor-methods invalid-option)

(defflavor invalid-option-value (object option value)
	   (user-error)
  :gettable-instance-variables
  :initable-instance-variables
  (:required-init-keywords :option :value :object))

(defmethod (invalid-option-value :report) (stream)
  (format stream "Invalid value ~s for option ~a, for object ~S"
	  value option object))

(compile-flavor-methods invalid-option-value)

(defflavor write-in-middle-of-tape (device-object)
	   (user-error)
  :gettable-instance-variables
  :initable-instance-variables
  (:required-init-keywords :device-object))

(defmethod (write-in-middle-of-tape :report) (stream)
  (format stream "User tried to write data in the middle of the tape on ~A."
	  device-object))

(compile-flavor-methods write-in-middle-of-tape)

(defflavor read-during-write (device-object)
	   (sys:no-action-mixin user-error)
  :gettable-instance-variables
  :initable-instance-variables
  (:required-init-keywords :device-object))

(defmethod (read-during-write :report) (stream)
  (format stream "User tried to examine the tape on ~a before completely writing it."
	  device-object))

(compile-flavor-methods read-during-write)

(defflavor not-supported (device-object operation)
	   (user-error)
  :gettable-instance-variables
  :initable-instance-variables
  (:required-init-keywords :device-object :operation))

(defmethod (not-supported :report) (stream)
  (format stream "Sorry, but ~s does not support the operation ~S.~%"
	  device-object operation))

(compile-flavor-methods not-supported)

;;; protocol violation - note: this is not necessarily the fault of
;;; the user specified values, but in some cases the fault of the format
;;; software.  It is assumed however that format software is checked and
;;; adheres to the protocols of the tape system, leaving user mistakes
;;; when using the device directly the most likely case.

(defflavor protocol-violation ()
	   (user-error)
  :gettable-instance-variables
  :initable-instance-variables
  (:required-init-keywords :format-string))

(defmethod (protocol-violation :report) (stream)
  (format stream "Protocol violation: ~A"
	  (lexpr-funcall 'format nil
			 eh:format-string
			 eh:format-args)))

(compile-flavor-methods protocol-violation)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; Higher level software errors - (i.e. in filesystem backup software)
;;;

(defflavor higher-level-sofware-error () (tape-error)
  :abstract-flavor)

