Initial revision
authorChris Hanson <org/chris-hanson/cph>
Thu, 10 Sep 1992 05:19:33 +0000 (05:19 +0000)
committerChris Hanson <org/chris-hanson/cph>
Thu, 10 Sep 1992 05:19:33 +0000 (05:19 +0000)
v7/src/6001/floppy.scm [new file with mode: 0644]

diff --git a/v7/src/6001/floppy.scm b/v7/src/6001/floppy.scm
new file mode 100644 (file)
index 0000000..405bd6c
--- /dev/null
@@ -0,0 +1,849 @@
+#| -*-Scheme-*-
+
+$Id: floppy.scm,v 1.1 1992/09/10 05:19:33 cph Exp $
+
+Copyright (c) 1992 Massachusetts Institute of Technology
+
+This material was developed by the Scheme project at the Massachusetts
+Institute of Technology, Department of Electrical Engineering and
+Computer Science.  Permission to copy this software, to redistribute
+it, and to use it for any purpose is granted, subject to the following
+restrictions and understandings.
+
+1. Any copy made of this software must include this copyright notice
+in full.
+
+2. Users of this software agree to make their best efforts (a) to
+return to the MIT Scheme project any improvements or extensions that
+they make, so that these may be included in future releases; and (b)
+to inform MIT of noteworthy uses of this software.
+
+3. All materials developed as a consequence of the use of this
+software shall duly acknowledge such use, in accordance with the usual
+standards of acknowledging credit in academic research.
+
+4. MIT has made no warrantee or representation that the operation of
+this software will be error-free, and MIT is under no obligation to
+provide any services, by way of maintenance, update, or otherwise.
+
+5. In conjunction with products arising from the use of this material,
+there shall be no use of the name of the Massachusetts Institute of
+Technology nor of any adaptation thereof in any advertising,
+promotional, or sales literature without prior written consent from
+MIT in each case. |#
+
+;;;; 6.001: Floppy Commands
+
+(declare (usual-integrations))
+\f
+;;;; Login and Logout
+
+(define (standard-login-initialization)
+  (with-editor-interrupts-disabled
+   (lambda ()
+     (set! floppy-contents-loaded? false)
+     (set-default-directory working-directory)
+     (set-working-directory-pathname! working-directory)
+     (standard-configuration 'login login-loop)))
+  (message "Login completed."))
+
+(define floppy-contents-loaded?)
+
+(define (login-loop)
+  (buffer-reset! (current-buffer))
+  (append-string
+   "Welcome to the 6.001 computer system.
+
+Please choose one of the following login options.
+
+To choose an option, just type the letter for that option.
+Case is not significant; you can use upper or lower case.
+Don't type RET after the letter.
+
+Here are the login options:
+
+F      First login.  Select this option if this is the first time you
+       are logging in this term.  You will be guided through the
+       process of initializing your floppies.
+
+L      Normal login.  Make sure that an initialized floppy is in the
+       drive before selecting this option.  The files on your floppy
+       will be copied into the computer so you can work on them.
+
+N      Login without floppy.  Select this option if you do not have
+       any floppies or if you do not wish to use a floppy.  WARNING:
+       IF YOU SELECT THIS OPTION YOU WILL NOT BE ABLE TO SAVE YOUR
+       WORK!
+
+Q      Quit.  Select this option if you do not want to log in.")
+  (let loop ()
+    (let ((char (prompt-for-char "Please choose an option")))
+      (case char
+       ((#\f #\F) (first-login))
+       ((#\l #\L) (normal-login))
+       ((#\n #\N) (no-floppy-login))
+       ((#\q #\Q) (exit-scheme))
+       (else (editor-beep) (loop))))))
+\f
+(define (first-login)
+  (append-string
+   "
+----------------------------------------------------------------------
+You have chosen the \"First login\" option.
+
+This login option will guide you through the steps of initializing
+two floppy disks.  You should have obtained these floppy disks from
+the instrument room.")
+  (login-common
+   (lambda ()
+     (append-string
+      "
+----------------------------------------------------------------------
+Please select one of your floppy disks, label it as your \"backup\"
+disk, and insert it into the drive.  When you have done this, type any
+character to continue.")
+     (wait-for-user)
+     (if (initialize-floppy)
+        (begin
+          (append-string
+           "
+----------------------------------------------------------------------
+Please eject your backup disk from the floppy drive.
+
+Now select your other disk, label it as your \"primary\" disk, and
+insert it into the floppy drive.  When you have done this, type any
+character to continue.")
+          (wait-for-user)
+          (if (initialize-floppy)
+              (begin
+                (set! floppy-contents-loaded? true)
+                (append-string "
+----------------------------------------------------------------------
+Your disks are now initialized.
+Type any character to finish logging in.")
+                (wait-for-user))
+              (login-loop)))
+        (login-loop)))))
+\f
+(define (normal-login)
+  (append-string
+   "
+----------------------------------------------------------------------
+You have chosen the \"Normal login\" option.
+
+This login option will read the contents of your floppy disk into the
+computer.  You should make sure that your disk is in the floppy drive.")
+  (login-common
+   (lambda ()
+     (let loop ()
+       (append-string
+       "
+----------------------------------------------------------------------
+")
+       (call-with-current-continuation
+       (lambda (k)
+         (handle-floppy-errors
+          (lambda () (within-continuation k loop))
+          (lambda () (within-continuation k login-loop))
+          (lambda ()
+            (load-from-floppy)
+            (set! floppy-contents-loaded? true)
+            unspecific))))))))
+
+(define (no-floppy-login)
+  (append-string
+   "
+----------------------------------------------------------------------
+You have chosen the \"Login without floppy\" option.
+
+This login option assumes that you have no floppy disk.  You should
+not use this login option if you have a floppy disk, because you
+cannot save your work unless your floppy is loaded when you login.
+IF YOU TRY TO SAVE YOUR WORK AFTER LOGGING IN WITH THIS OPTION, THE
+FILES ON YOUR FLOPPY WILL BE DELETED.")
+  (login-common
+   (lambda ()
+     (set! floppy-contents-loaded? false)
+     unspecific)))
+
+(define (login-common thunk)
+  (append-string
+   "
+
+If you have chosen this login option by mistake, please type the
+letter N, which will return you to the login loop.
+
+Otherwise, type Y to continue with the initialization process.")
+  (if (prompt-for-confirmation? "Continue with login")
+      (thunk)
+      (login-loop)))
+\f
+(define-command logout
+  "Logout from the 6.001 Scheme system."
+  ()
+  (lambda ()
+    (standard-configuration 'logout
+      (lambda ()
+       (fluid-let ((paranoid-exit? false))
+         (save-buffers-and-exit false "Scheme"
+           (lambda ()
+             (let ((abort
+                    (lambda ()
+                      (append-string
+                       "
+If you want to log out without saving your files, answer \"yes\" to
+the question below.
+
+Answer \"no\" if you want to return to the editor without logging out.")
+                      (if (prompt-for-yes-or-no?
+                           "Log out without saving files")
+                          (exit-scheme)
+                          (begin
+                            (append-string
+                             "
+----------------------------------------------------------------------
+Use M-x checkpoint-floppy to save your files.
+")
+                            (sit-for (* 5 1000))
+                            (abort-current-command))))))
+               (if (not floppy-contents-loaded?)
+                   (begin
+                     (append-string "You logged in without a floppy disk.\n")
+                     (abort)))
+               (let loop ()
+                 (call-with-current-continuation
+                  (lambda (k)
+                    (handle-floppy-errors
+                     (lambda ()
+                       (append-string
+                        "
+----------------------------------------------------------------------
+")
+                       (within-continuation k loop))
+                     (lambda ()
+                       (append-string
+                        "
+----------------------------------------------------------------------")
+                       (abort))
+                     checkpoint-floppy)))))
+             (exit-scheme))))))))
+\f
+;;;; Initialize Floppy
+
+(define-command initialize-floppy
+  "Initialize a floppy disk.
+Requests confirmation unless a prefix arg is given."
+  "P"
+  (lambda (no-confirmation?)
+    (standard-configuration 'initialize-floppy
+      (lambda ()
+       (if (or no-confirmation?
+               (begin
+                 (append-string
+                  "This command initializes a floppy disk.
+The disk will be formatted as a DOS disk.
+If you have any data on the disk, it will be destroyed.
+
+The disk must be a high-density disk; high-density disks are black and
+are marked \"HD\" in one corner.  These disks are also often marked
+\"DS/HD\" somewhere.
+
+If you want to continue, please insert a disk in the floppy drive,
+then answer \"yes\" to the prompt below.")
+                 (prompt-for-yes-or-no?
+                  "Proceed with floppy disk initialization")))
+           (if (initialize-floppy)
+               (begin
+                 (append-string "
+
+Floppy successfully initialized.")
+                 (if (not no-confirmation?)
+                     (wait-for-user)))
+               (message "OK, not initializing floppy.")))))))
+\f
+(define (initialize-floppy)
+  (let loop ()
+    (append-string
+     "
+----------------------------------------------------------------------
+Initializing floppy.
+
+Please wait, this will take about five minutes.
+
+Please do not type control-G; if you abort the initialization you will
+be unable to use the floppy unless you initialize it again.
+")
+    (call-with-current-continuation
+     (lambda (k)
+       (bind-condition-handler (list condition-type:floppy-error)
+          (lambda (condition)
+            (append-string
+             "
+If you want to continue, please ")
+            (let ((type (condition/type condition)))
+              (cond ((eq? type condition-type:no-floppy-in-drive)
+                     (append-string "insert a floppy disk"))
+                    ((eq? type condition-type:floppy-drive-busy)
+                     (append-string
+                      "eject the floppy disk and re-insert it"))
+                    (else
+                     (append-string "correct the error")
+                     (let ((buffer
+                            (temporary-buffer " *floppy-error*")))
+                       (insert-string
+                        (floppy-error/output condition)
+                        (buffer-start buffer))
+                       (set-buffer-point! buffer
+                                          (buffer-start buffer))
+                       (set-buffer-read-only! buffer)
+                       (pop-up-buffer buffer false)))))
+            (append-string
+             ",
+then answer \"yes\" to the prompt below.")
+            (if (prompt-for-yes-or-no?
+                 "Proceed with floppy disk initialization")
+                (within-continuation k
+                  (lambda ()
+                    (delete-other-windows (current-window))
+                    (loop)))
+                (k false)))
+        (lambda ()
+          (mediainit-floppy)
+          (make-floppy-file-system)
+          true))))))
+\f
+(define (mediainit-floppy)
+  (append-string "\nFormatting disk...")
+  (call-with-temporary-buffer " *mediainit-floppy*"
+    (lambda (buffer)
+      (let ((result
+            (shell-command false (buffer-start buffer) false false
+                           mediainit-command))
+           (lose
+            (lambda (condition-type)
+              (error condition-type
+                     'COMMAND mediainit-command
+                     'OUTPUT (buffer->string buffer)))))
+       (if (equal? result '(EXITED . 0))
+           (append-string "done")
+           (begin
+             (editor-beep)
+             (append-string "ERROR\n")
+             (if (equal? result '(EXITED . 1))
+                 (let ((message
+                        (let ((start (buffer-start buffer)))
+                          (extract-string start (line-end start 0)))))
+                   (cond ((string=? message mediainit-floppy-missing)
+                          (append-string
+                           "\nThere is no disk in the floppy drive.")
+                          (lose condition-type:no-floppy-in-drive))
+                         ((string=? message mediainit-floppy-busy)
+                          (append-string "\nThe floppy drive is busy.")
+                          (lose condition-type:floppy-drive-busy))
+                         (else
+                          (lose condition-type:floppy-error))))
+                 (lose condition-type:floppy-error))))))))
+
+(define mediainit-command
+  "/usr/bin/mediainit -i 2 -f 16 /dev/rfd")
+
+(define mediainit-floppy-missing
+  "mediainit: initialize media command failed - No such device or address")
+
+(define mediainit-floppy-busy
+  "mediainit: can't open file /dev/rfd - Device busy")
+
+(define (make-floppy-file-system)
+  (append-string "\nCreating file system...")
+  (call-with-temporary-buffer " *make-floppy-fs*"
+    (lambda (buffer)
+      (if (equal? '(EXITED . 0)
+                 (shell-command false (buffer-start buffer) false false
+                                make-floppy-fs-command))
+         (append-string "done")
+         (error condition-type:floppy-error
+                'COMMAND make-floppy-fs-command
+                'OUTPUT (buffer->string buffer))))))
+
+(define make-floppy-fs-command
+  "/bin/dd if=/usr/local/lib/ibm-image.dd of=/dev/rfd obs=18k")
+\f
+;;;; Reading and Writing Floppy
+
+(define-command load-from-floppy
+  "Copy the files from a floppy disk to the working directory."
+  ()
+  (lambda ()
+    (standard-configuration 'load-from-floppy
+      (lambda ()
+       (load-from-floppy)))))
+
+(define (load-from-floppy)
+  (append-string "Loading files from floppy to working directory.\n")
+  (let ((records (read-floppy-directory)))
+    (if (null? records)
+       (append-string "\nFloppy has no files to load.")
+       (for-each (lambda (record)
+                   (append-string
+                    (string-append
+                     "\nLoading file \""
+                     (file-record/name record)
+                     "\"..."))
+                   (read-floppy-file record)
+                   (append-string "done"))
+                 records)))
+  (set! floppy-contents-loaded? true)
+  (append-string "\n\nFloppy contents loaded.")
+  (wait-for-user))
+
+(define-command checkpoint-floppy
+  "Update a floppy disk to contain the same files as the working directory."
+  ()
+  (lambda ()
+    (standard-configuration 'checkpoint-floppy
+      (lambda ()
+       (checkpoint-floppy)
+       (append-string "\n\nFloppy checkpoint finished.")
+       (wait-for-user)))))
+
+(define (checkpoint-floppy)
+  (append-string "Copying files from working directory to floppy.\n")
+  (let* ((working-directory (read-working-directory))
+        (floppy-directory (read-floppy-directory)))
+    (with-values
+       (lambda ()
+         (three-way-sort file-record/name=?
+                         working-directory
+                         floppy-directory))
+      (lambda (files-to-copy pairs files-to-delete)
+       (for-each (lambda (pair)
+                   (if (> (file-record/time (car pair))
+                          (file-record/time (cdr pair)))
+                       (set! files-to-copy
+                             (cons (car pair) files-to-copy))))
+                 pairs)
+       (if (null? files-to-delete)
+           (append-string "\nThere are no files to delete.")
+           (for-each (lambda (record)
+                       (append-string
+                        (string-append "\nDeleting file \""
+                                       (file-record/name record)
+                                       "\"..."))
+                       (delete-floppy-file record)
+                       (append-string "done"))
+                     files-to-delete))
+       (if (null? files-to-copy)
+           (append-string "\nThere are no files to copy.")
+           (for-each (lambda (record)
+                       (append-string
+                        (string-append "\nCopying file \""
+                                       (file-record/name record)
+                                       "\"..."))
+                       (write-floppy-file record)
+                       (append-string "done"))
+                     files-to-copy))))))
+\f
+(define (read-working-directory)
+  (append-string "\nReading working directory...")
+  (let ((result
+        (map (lambda (pathname)
+               (make-file-record
+                (file-namestring pathname)
+                (* (quotient (file-modification-time pathname) 60) 60)))
+             (list-transform-negative (directory-read working-directory)
+               file-directory?)))
+       (valid-dos-record?
+        (lambda (record)
+          (valid-dos-filename? (file-record/name record)))))
+    (append-string "done")
+    (let ((non-dos (list-transform-negative result valid-dos-record?)))
+      (if (null? non-dos)
+         result
+         (begin
+           (append-string
+            "
+
+The following files have names that are not valid for DOS disks:
+
+")
+           (for-each (lambda (record)
+                       (append-string (file-record/name record)))
+                     non-dos)
+           (append-string
+            "
+
+These files cannot be saved to your floppy disk.
+If you want to save them, you must rename them to valid DOS filenames.
+
+To abort this command, giving you the chance to rename these files,
+answer \"yes\" to the question below.
+
+To continue with this command, saving all files EXCEPT these,
+answer \"no\" to the question below.")
+           (if (prompt-for-yes-or-no? "Abort this command")
+               (begin
+                 (append-string
+                  "
+----------------------------------------------------------------------
+To see a description of valid DOS filenames, use the command
+M-x describe-dos-filenames.  To rename your files, use the command
+M-x rename-file, or use the `r' command in Dired.")
+                 (sit-for (* 5 1000))
+                 (*floppy-abort-handler*))
+               (begin
+                 (append-string
+                  "
+----------------------------------------------------------------------")
+                 (list-transform-positive result valid-dos-record?))))))))
+
+(define-command describe-dos-filenames
+  "Describe the format of DOS filenames."
+  ()
+  (lambda ()
+    (with-output-to-help-display
+     (lambda ()
+       (write-string dos-filename-description)
+       (newline)))))
+\f
+(define (write-floppy-file record)
+  ;; It is necessary to delete the file before writing it, because
+  ;; otherwise the modification time is not updated.  Furthermore we
+  ;; must handle errors because the dosrm command might fail.
+  (let ((dos-name (file-record/dos-name record)))
+    (call-with-current-continuation
+     (lambda (continue)
+       (bind-condition-handler (list condition-type:floppy-error)
+          (lambda (condition)
+            (if (string-prefix? "/usr/bin/dosrm: cannot open "
+                                (floppy-error/output condition))
+                (continue unspecific)))
+        (lambda ()
+          (run-dosrm-command dos-name)))))
+    (run-doscp-command (file-record/unix-name record) dos-name)))
+
+(define (read-floppy-file record)
+  (let ((unix-name (file-record/unix-name record)))
+    (run-doscp-command (file-record/dos-name record) unix-name)
+    (set-file-times! unix-name false (file-record/time record))))
+
+(define (delete-floppy-file record)
+  (run-dosrm-command (file-record/dos-name record)))
+
+(define (read-floppy-directory)
+  (append-string "\nReading floppy directory...")
+  (let ((result (dosls floppy-directory)))
+    (append-string "done")
+    result))
+
+(define (dos-file-modification-time dos-name)
+  (let ((records (dosls dos-name)))
+    (if (null? records)
+       (error "No output from dosls command."))
+    (if (not (null? (cdr records)))
+       (error "Too much output from dosls command."))
+    (file-record/time (car records))))
+\f
+;;;; Reading Floppy Directory
+
+(define (dosls directory)
+  (let ((contents (run-dosll-command directory)))
+    (parse-dosls-output contents 0 (string-length contents))))
+
+(define parse-dosls-output
+  (let ((leader-pattern
+        (re-compile-pattern "\nThe DOS Volume Label is +\\([^ \n]*\\) *\n *\n"
+                            false)))
+    (lambda (string start end)
+      (let ((offset (time-zone-offset)))
+       (let loop
+           ((start
+             (if (re-match-substring-forward leader-pattern false false
+                                             string start end)
+                 (re-match-end-index 0)
+                 start)))
+         (if (= start end)
+             '()
+             (let ((eol
+                    (or (substring-find-next-char string start end #\newline)
+                        end)))
+               (with-values
+                   (lambda ()
+                     (parse-dosls-line string start eol offset))
+                 (lambda (filename time)
+                   (let ((records (if (= eol end) '() (loop (+ eol 1)))))
+                     (if (directory-filename? filename)
+                         records
+                         (cons (make-file-record filename time)
+                               records))))))))))))
+
+(define parse-dosls-line
+  (let ((line-pattern
+        (re-compile-pattern
+         (string-append "^ *[0-9]+ +[0-9]+ +"
+                        "\\([a-zA-z]+\\) +\\([0-9]+\\) +"
+                        "\\([0-9][0-9][0-9][0-9]\\) +"
+                        "\\([0-9][0-9]\\):\\([0-9][0-9]\\) +"
+                        "/dev/rfd:/\\(.+\\) *$")
+         false)))
+    (lambda (string start end offset)
+      (if (not (re-match-substring-forward line-pattern false false
+                                          string start end))
+         (error "Line doesn't match dosls -l pattern:"
+                (substring string start end)))
+      (let ((month (extract-string-match string 1))
+           (day (extract-string-match string 2))
+           (year (extract-string-match string 3))
+           (hour (extract-string-match string 4))
+           (minute (extract-string-match string 5))
+           (filename (extract-string-match string 6)))
+       (values (string-downcase filename)
+               (+ (make-dos-time (string->number year)
+                                 (month-name->number month)
+                                 (string->number day)
+                                 (string->number hour)
+                                 (string->number minute))
+                  offset))))))
+\f
+(define (month-name->number month)
+  (let ((months
+        '#("Jan" "Feb" "Mar" "Apr" "May" "Jun"
+                 "Jul" "Aug" "Sep" "Oct" "Nov" "Dec")))
+    (let loop ((index 0))
+      (if (= index 12)
+         (error "Unknown month string:" month))
+      (if (string-prefix-ci? (vector-ref months index) month)
+         (+ index 1)
+         (loop (+ index 1))))))
+
+(define (make-dos-time year month day hour minute)
+  (* 60
+     (+ minute
+       (* 60
+          (+ hour
+             (* 24
+                (+ (- day 1)
+                   (vector-ref '#(0 31 59 90 120 151 181 212 243 273 304 334)
+                               (- month 1))
+                   (if (and (= 0 (remainder year 4)) (> month 2))
+                       1
+                       0)
+                   (- (quotient (- year 1) 4) (quotient 1970 4))
+                   (* 365 (- year 1970)))))))))
+
+(define (time-zone-offset)
+  (let ((decoded-time (get-decoded-time))
+       (file-time (unix/current-file-time)))
+    (- (* (quotient file-time 60) 60)
+       (make-dos-time (decoded-time/year decoded-time)
+                     (decoded-time/month decoded-time)
+                     (decoded-time/day decoded-time)
+                     (decoded-time/hour decoded-time)
+                     (decoded-time/minute decoded-time)))))
+
+(define-structure (file-record (conc-name file-record/))
+  (name false read-only true)
+  time)
+
+(define (file-record/dos-name record)
+  (string-append floppy-directory (file-record/name record)))
+
+(define floppy-directory
+  "/dev/rfd:/")
+
+(define (file-record/unix-name record)
+  (string-append working-directory (file-record/name record)))
+
+(define working-directory
+  "/users/u6001/work/")
+
+(define (file-record/name=? x y)
+  (string=? (file-record/name x) (file-record/name y)))
+\f
+;;;; Floppy Command Subprocesses
+
+(define (run-dosll-command directory)
+  (run-dos-command (string-append "/usr/bin/dosls -Al " directory)))
+
+(define (run-doscp-command from to)
+  (run-dos-command (string-append "/usr/bin/doscp -f " from " " to)))
+
+(define (run-dosrm-command filename)
+  (run-dos-command (string-append "/usr/bin/dosrm -f " filename)))
+
+(define (run-dos-command command)
+  (call-with-temporary-buffer " *dos-floppy-command*"
+    (lambda (buffer)
+      (let ((result
+            (shell-command false (buffer-start buffer) false false command)))
+       (let ((output
+              (extract-string (buffer-start buffer) (buffer-end buffer))))
+         (if (equal? result '(EXITED . 0))
+             output
+             ((cond ((not (equal? result '(EXITED . 1)))
+                     make-condition:floppy-error)
+                    ((string-prefix? no-floppy-in-drive output)
+                     make-condition:no-floppy-in-drive)
+                    ((string-prefix? non-dos-floppy-in-drive output)
+                     make-condition:non-dos-floppy-in-drive)
+                    (else
+                     make-condition:floppy-error))
+              command output)))))))
+
+(define no-floppy-in-drive
+  "Error reading. block = 0 on device /dev/rfd")
+
+(define non-dos-floppy-in-drive
+  "Unrecognizable disc format on /dev/rfd")
+\f
+;;;; Floppy Errors
+
+(define (handle-floppy-errors continue abort thunk)
+  (fluid-let ((*floppy-abort-handler* abort))
+    (bind-condition-handler (list condition-type:floppy-error)
+       (lambda (condition)
+         (append-string
+          (string-append
+           "ERROR\n\n"
+           (condition/report-string condition)
+           "\n\nTo try again, please "
+           (if (let ((type (condition/type condition)))
+                 (or (eq? type condition-type:no-floppy-in-drive)
+                     (eq? type condition-type:non-dos-floppy-in-drive)))
+               "insert a DOS floppy"
+               "correct the error")
+           ",\nthen answer \"yes\" to the question below."))
+         (if (prompt-for-yes-or-no? "Try again")
+             (continue)
+             (abort)))
+      thunk)))
+
+(define *floppy-abort-handler*)
+
+(define (default-floppy-abort-handler)
+  (message "OK, aborting command")
+  (abort-current-command))
+
+(define condition-type:floppy-error
+  (make-condition-type 'FLOPPY-ERROR condition-type:error
+      '(COMMAND OUTPUT)
+    (lambda (condition port)
+      condition
+      (write-string "An error occurred while accessing the floppy.\n" port)
+      (write-string "The command being executed was:\n\n" port)
+      (write-string (floppy-error/command condition) port)
+      (write-string "\n\nThe output from the command was:\n\n" port)
+      (write-string (floppy-error/output condition) port))))
+
+(define make-condition:floppy-error
+  (condition-signaller condition-type:floppy-error
+                      '(COMMAND OUTPUT)
+                      standard-error-handler))
+
+(define floppy-error/command
+  (condition-accessor condition-type:floppy-error 'COMMAND))
+
+(define floppy-error/output
+  (condition-accessor condition-type:floppy-error 'OUTPUT))
+
+(define condition-type:no-floppy-in-drive
+  (make-condition-type 'NO-FLOPPY-IN-DRIVE condition-type:floppy-error '()
+    (lambda (condition port)
+      condition
+      (write-string "No floppy disk in drive." port))))
+
+(define make-condition:no-floppy-in-drive
+  (condition-signaller condition-type:no-floppy-in-drive
+                      '(COMMAND OUTPUT)
+                      standard-error-handler))
+
+(define condition-type:floppy-drive-busy
+  (make-condition-type 'FLOPPY-DRIVE-BUSY condition-type:floppy-error '()
+    (lambda (condition port)
+      condition
+      (write-string "The floppy drive is busy." port))))
+
+(define condition-type:non-dos-floppy-in-drive
+  (make-condition-type 'NON-DOS-FLOPPY-IN-DRIVE condition-type:floppy-error '()
+    (lambda (condition port)
+      condition
+      (write-string "Floppy disk in drive is not DOS format." port))))
+
+(define make-condition:non-dos-floppy-in-drive
+  (condition-signaller condition-type:non-dos-floppy-in-drive
+                      '(COMMAND OUTPUT)
+                      standard-error-handler))
+\f
+;;;; Miscellaneous
+
+(define (directory-filename? filename)
+  (char=? #\/ (string-ref filename (- (string-length filename) 1))))
+
+(define (extract-string-match string n)
+  (substring string (re-match-start-index n) (re-match-end-index n)))
+
+(define (three-way-sort = set set*)
+  (let ((member? (member-procedure =)))
+    (let loop ((set set) (set* (list-copy set*)))
+      (if (null? set)
+         (values '() '() set*)
+         (let ((item (member? (car set) set*)))
+           (if item
+               (with-values
+                   (lambda () (loop (cdr set) (delq! (car item) set*)))
+                 (lambda (set-only both set*-only)
+                   (values set-only
+                           (cons (cons (car set) (car item)) both)
+                           set*-only)))
+               (with-values (lambda () (loop (cdr set) set*))
+                 (lambda (set-only both set*-only)
+                   (values (cons (car set) set-only)
+                           both
+                           set*-only)))))))))
+
+(define (standard-configuration command thunk)
+  (let loop ()
+    (call-with-current-continuation
+     (lambda (k)
+       (with-saved-configuration
+       (lambda ()
+         (delete-other-windows (current-window))
+         (let ((buffer
+                (temporary-buffer
+                 (string-append
+                  " *"
+                  (symbol->string command)
+                  "-dialog*"))))
+           (select-buffer buffer)
+           (handle-floppy-errors
+            (lambda () (within-continuation k loop))
+            default-floppy-abort-handler
+            thunk))))))))
+
+(define (with-saved-configuration thunk)
+  (let ((screen (selected-screen)))
+    (let ((configuration (screen-window-configuration screen)))
+      (fluid-let ((restore-saved-continuation? true))
+       (dynamic-wind
+        (lambda () unspecific)
+        thunk
+        (lambda ()
+          (if restore-saved-continuation?
+              (set-screen-window-configuration! screen configuration))))))))
+
+(define (dont-restore-saved-configuration)
+  (set! restore-saved-continuation? false)
+  unspecific)
+
+(define restore-saved-continuation?)
+
+(define (append-string string)
+  (insert-string string)
+  (update-screens! false)
+  (sit-for 0))
+
+(define (buffer->string buffer)
+  (extract-string (buffer-start buffer) (buffer-end buffer)))
+
+(define (wait-for-user)
+  ;; This should ignore input events (like focus change).
+  (prompt-for-char "Type any character to continue"))
\ No newline at end of file