X-Git-Url: https://git.distorted.org.uk/~mdw/lisp/blobdiff_plain/623291d25eb6f31558f252f037997549171b4d24..1a50efd8a9e976bf16a29ce381ea38c6b9a39ea1:/safely.lisp diff --git a/safely.lisp b/safely.lisp index 44a707e..9b4ab45 100644 --- a/safely.lisp +++ b/safely.lisp @@ -13,12 +13,12 @@ ;;; it under the terms of the GNU General Public License as published by ;;; the Free Software Foundation; either version 2 of the License, or ;;; (at your option) any later version. -;;; +;;; ;;; This program is distributed in the hope that it will be useful, ;;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;;; GNU General Public License for more details. -;;; +;;; ;;; You should have received a copy of the GNU General Public License ;;; along with this program; if not, write to the Free Software Foundation, ;;; Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. @@ -30,6 +30,17 @@ #:safely-writing)) (in-package #:safely) +#+(or cmu sbcl) +(eval-when (:compile-toplevel :execute) + (import #+cmu '(ext:unix-namestring unix:unix-link) + #+sbcl '(sb-ext:native-namestring))) +#+cmu +(progn + (declaim (inline unix-namestring)) + (defun native-namestring (pathname &key as-file) + (declare (ignore as-file)) + (unix-namestring pathname nil))) + (defstruct (safely (:predicate safelyp)) "Stores information about how to commit or undo safe writes." (streams nil) @@ -99,6 +110,13 @@ (safely-trail safe)) stream)) +(declaim (inline rename)) +(defun rename (old new) + (let ((target (make-pathname :directory '(:relative) + :defaults new))) + #-clisp (rename-file old target) + #+clisp (rename-file old target :if-exists :overwrite))) + (defun delete-file-without-moaning (file) "Delete the FILE, ignoring errors." (handler-case (delete-file file) @@ -107,7 +125,7 @@ (defun rename-file-without-moaning (old new) "Rename OLD to NEW, ignoring errors, and without doing any stupid name mangling." - (handler-case (rename-file old new) + (handler-case (rename old new) (file-error () nil))) (defun safely-unwind (trail) @@ -136,19 +154,34 @@ (safely-unwind (safely-trail safe)) (safely-reset safe)) +#+sbcl +(defun unix-link (from to) + (sb-unix::int-syscall ("link" sb-alien:c-string sb-alien:c-string) + from to)) + (defun safe-copy (file tag) "Make a copy of the FILE. Return the new name." - #+cmu + #+(or cmu sbcl) ;; Use link(2) where available. (generate-fresh-file-name file tag (lambda (name) - (let ((from (ext:unix-namestring file t)) - (to (ext:unix-namestring name nil))) + (let ((from (native-namestring file + :as-file t)) + (to (native-namestring name + :as-file t))) (and from to - (unix:unix-link from to))))) + (unix-link from to))))) - #-cmu + #+clisp + (generate-fresh-file-name file tag + (lambda (name) + (posix:copy-file (namestring file) + (namestring name) + :method :hardlink + :if-exists nil))) + + #-(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 @@ -171,7 +204,7 @@ (when (< read (length buffer)) (return copy)))))) (close output))))) - + (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 @@ -187,23 +220,23 @@ (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-file 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) @@ -227,8 +260,7 @@ temporary file, and if BODY completes, it is renamed to FILE." (with-gensyms safe `(safely (,safe) - (let ((,stream (apply #'safely-open-output-stream - ,safe ,file ,open-args))) + (let ((,stream (safely-open-output-stream ,safe ,file ,@open-args))) ,@body)))) ;;;----- That's all, folks --------------------------------------------------