;;; -*-lisp-*-
;;;
-;;; $Id$
-;;;
;;; Safely modify collections of files
;;;
;;; (c) 2005 Straylight/Edgeware
;;; Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
(defpackage #:safely
- (:use #:common-lisp #:mdw.base)
- (:export #:safely #:safely-close #:safely-delete-file
- #:safely-open-output-stream #:safely-bail #:safely-commit
- #:safely-writing))
+ (:use #:common-lisp #:mdw.base))
(in-package #:safely)
#+(or cmu sbcl)
(declare (ignore as-file))
(unix-namestring pathname nil)))
-(defstruct (safely (:predicate safelyp))
+(export '(safely safelyp make-safely))
+(defstruct (safely (:predicate safelyp) (:constructor make-safely ()))
"Stores information about how to commit or undo safe writes."
(streams nil)
(trail nil))
+(export 'safely-close)
(defun safely-close (safe stream)
"Make sure that STREAM is closed when SAFE is finished."
(push stream (safely-streams safe)))
+(export 'safely-delete-file)
(defun safely-delete-file (safe file)
"Delete FILE when SAFE is committed."
(push `(:delete ,file) (safely-trail safe)))
(when ret
(return (values new ret)))))))
+(export 'safely-open-output-stream)
(defun safely-open-output-stream (safe file &rest open-args)
"Create an output stream which will be named FILE when SAFE is committed.
Other OPEN-ARGS are passed to open."
(safely-trail safe))
stream))
+#+clisp
+(progn
+ (ffi:def-call-out %rename (:name "rename")
+ (:language :stdc)
+ (:library "libc.so.6")
+ (:arguments (from ffi:c-string)
+ (to ffi:c-string))
+ (:return-type ffi:int)))
+
(declaim (inline rename))
(defun rename (old new)
- #-clisp (rename-file old new)
- #+clisp (posix:copy-file old new :method :rename))
+ #-clisp
+ (let ((target (make-pathname :directory '(:relative)
+ :defaults new)))
+ (rename-file old target))
+
+ #+clisp
+ (let ((rc (%rename (namestring old) (namestring new))))
+ (when (= rc -1)
+ (error "Failed to rename ~S to ~S: ~A" old new (posix:strerror)))))
(defun delete-file-without-moaning (file)
"Delete the FILE, ignoring errors."
(setf (safely-streams safe) nil)
(setf (safely-trail safe) nil))
+(export 'safely-bail)
(defun safely-bail (safe)
"Abort the operations in SAFE, unwinding all the things that have been
done. Streams are closed, new files are removed."
:method :hardlink
:if-exists nil)))
-
-
- #-(or cmu sbcl)
+ #-(or cmu sbcl clisp)
;; Otherwise just copy the file contents and hope for the best.
(with-open-file (input file :element-type :default)
(multiple-value-bind
(return copy))))))
(close output)))))
+(export 'safely-commit)
(defun safely-commit (safe)
"Commit SAFE. The files deleted by safely-delete-file are deleted; the
files created by safely-open-output-stream are renamed over the old
(loop
(unless trail
(return))
- (let ((job (pop trail)))
- (ecase (car job)
- (:shunt (destructuring-bind (tag new file) job
- (declare (ignore tag))
- (push `(:rmtmp ,new) revert)
- (if (probe-file file)
- (let ((old (safe-copy file "old")))
- (push `(:rmtmp ,old) cleanup)
- (push `(:revert ,old ,file) revert))
- (push `(:rmtmp ,file) revert))
- (rename new file)))
- (:delete (destructuring-bind (tag file) job
- (declare (ignore tag))
- (let ((old (safe-copy file "delete")))
- (push `(:revert ,old ,file) revert)
- (push `(:rmtmp ,old) cleanup)
- (delete-file file)))))))
+ (let ((job (pop trail)))
+ (ecase (car job)
+ (:shunt (destructuring-bind (tag new file) job
+ (declare (ignore tag))
+ (push `(:rmtmp ,new) revert)
+ (if (probe-file file)
+ (let ((old (safe-copy file "old")))
+ (push `(:rmtmp ,old) cleanup)
+ (push `(:revert ,old ,file) revert))
+ (push `(:rmtmp ,file) revert))
+ (rename new file)))
+ (:delete (destructuring-bind (tag file) job
+ (declare (ignore tag))
+ (let ((old (safe-copy file "delete")))
+ (push `(:revert ,old ,file) revert)
+ (push `(:rmtmp ,old) cleanup)
+ (delete-file file)))))))
(setf revert nil))
(safely-unwind trail)
(safely-unwind revert)
(safely-unwind cleanup)
(safely-reset safe))))
+;; The symbol `safely' is already exported.
(defmacro safely ((safe &key) &body body)
"Do stuff within the BODY safely. If BODY completes without errors, the
SAFE is committed; otherwise it's bailed."
(when ,safe
(safely-bail ,safe)))))
+(export 'safely-writing)
(defmacro safely-writing ((stream file &rest open-args) &body body)
"Simple macro for writing a single file safely. STREAM is opened onto a
temporary file, and if BODY completes, it is renamed to FILE."