X-Git-Url: https://code.delx.au/gnu-emacs/blobdiff_plain/a457417ee5ba797ab1c91d35ee957bb7a7f8d4b6..0e90e7befb01afa55de1657e2ca719fac04df489:/lisp/tar-mode.el diff --git a/lisp/tar-mode.el b/lisp/tar-mode.el index 80e642d666..5da38db052 100644 --- a/lisp/tar-mode.el +++ b/lisp/tar-mode.el @@ -1,7 +1,7 @@ ;;; tar-mode.el --- simple editing of tar files from GNU emacs ;; Copyright (C) 1990, 1991, 1993, 1994, 1995, 1996, 1997, 1998, 1999, -;; 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007 Free Software Foundation, Inc. +;; 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 Free Software Foundation, Inc. ;; Author: Jamie Zawinski ;; Maintainer: FSF @@ -10,10 +10,10 @@ ;; This file is part of GNU Emacs. -;; GNU Emacs is free software; you can redistribute it and/or modify +;; GNU Emacs is free software: you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by -;; the Free Software Foundation; either version 3, or (at your option) -;; any later version. +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. ;; GNU Emacs is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -21,9 +21,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs; see the file COPYING. If not, write to the -;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, -;; Boston, MA 02110-1301, USA. +;; along with GNU Emacs. If not, see . ;;; Commentary: @@ -93,8 +91,15 @@ ;; some scratch directory would be very wasteful, and wouldn't be able to ;; preserve the file owners. +;;; Bugs: + +;; - Rename on ././@LongLink files +;; - Revert confirmation displays the raw data temporarily. + ;;; Code: +(eval-when-compile (require 'cl)) + (defgroup tar nil "Simple editing of tar files." :prefix "tar-" @@ -129,58 +134,71 @@ This information is useful, but it takes screen space away from file names." :group 'tar) (defvar tar-parse-info nil) -;; Be sure that this variable holds byte position, not char position. -(defvar tar-header-offset nil) (defvar tar-superior-buffer nil) (defvar tar-superior-descriptor nil) (defvar tar-subfile-mode nil) +(defvar tar-file-name-coding-system nil) -(put 'tar-parse-info 'permanent-local t) -(put 'tar-header-offset 'permanent-local t) (put 'tar-superior-buffer 'permanent-local t) (put 'tar-superior-descriptor 'permanent-local t) - -(defmacro tar-setf (form val) - "A mind-numbingly simple implementation of setf." - (let ((mform (macroexpand form (and (boundp 'byte-compile-macro-environment) - byte-compile-macro-environment)))) - (cond ((symbolp mform) (list 'setq mform val)) - ((not (consp mform)) (error "can't setf %s" form)) - ((eq (car mform) 'aref) - (list 'aset (nth 1 mform) (nth 2 mform) val)) - ((eq (car mform) 'car) - (list 'setcar (nth 1 mform) val)) - ((eq (car mform) 'cdr) - (list 'setcdr (nth 1 mform) val)) - (t (error "don't know how to setf %s" form))))) + +;; The Tar data is made up of bytes and better manipulated as bytes +;; and can be very large, so insert/delete can be costly. The summary we +;; want to display may contain non-ascci chars, of course, so we'd like it +;; to be multibyte. We used to keep both in the same buffer and switch +;; from/to uni/multibyte. But this had several downsides: +;; - set-buffer-multibyte has an O(N^2) worst case that tends to be triggered +;; here, so it gets atrociously slow on large Tar files. +;; - need to widen/narrow the buffer to show/hide the raw data, and need to +;; maintain a tar-header-offset that keeps track of the boundary between +;; the two. +;; - can't use markers because they're not preserved by set-buffer-multibyte. +;; So instead, we now keep the two pieces of data in separate buffers, and +;; use the new buffer-swap-text primitive when we need to change which data +;; is associated with "the" buffer. +(defvar tar-data-buffer nil "Buffer that holds the actual raw tar bytes.") +(make-variable-buffer-local 'tar-data-buffer) + +(defvar tar-data-swapped nil + "If non-nil, `tar-data-buffer' indeed holds raw tar bytes.") +(make-variable-buffer-local 'tar-data-swapped) + +(defun tar-data-swapped-p () + "Return non-nil if the tar-data is in `tar-data-buffer'." + (and (buffer-live-p tar-data-buffer) + ;; Sanity check to try and make sure tar-data-swapped tracks the swap + ;; state correctly: the raw data is expected to be always larger than + ;; the summary. + (progn + (assert (eq tar-data-swapped + (> (buffer-size tar-data-buffer) (buffer-size)))) + tar-data-swapped))) + +(defun tar-swap-data () + "Swap buffer contents between current buffer and `tar-data-buffer'. +Preserve the modified states of the buffers and set `buffer-swapped-with'." + (let ((data-buffer-modified-p (buffer-modified-p tar-data-buffer)) + (current-buffer-modified-p (buffer-modified-p))) + (buffer-swap-text tar-data-buffer) + (setq tar-data-swapped (not tar-data-swapped)) + (restore-buffer-modified-p data-buffer-modified-p) + (with-current-buffer tar-data-buffer + (restore-buffer-modified-p current-buffer-modified-p)))) ;;; down to business. -(defmacro make-tar-header (name mode uid git size date ck lt ln - magic uname gname devmaj devmin) - (list 'vector name mode uid git size date ck lt ln - magic uname gname devmaj devmin)) - -(defmacro tar-header-name (x) (list 'aref x 0)) -(defmacro tar-header-mode (x) (list 'aref x 1)) -(defmacro tar-header-uid (x) (list 'aref x 2)) -(defmacro tar-header-gid (x) (list 'aref x 3)) -(defmacro tar-header-size (x) (list 'aref x 4)) -(defmacro tar-header-date (x) (list 'aref x 5)) -(defmacro tar-header-checksum (x) (list 'aref x 6)) -(defmacro tar-header-link-type (x) (list 'aref x 7)) -(defmacro tar-header-link-name (x) (list 'aref x 8)) -(defmacro tar-header-magic (x) (list 'aref x 9)) -(defmacro tar-header-uname (x) (list 'aref x 10)) -(defmacro tar-header-gname (x) (list 'aref x 11)) -(defmacro tar-header-dmaj (x) (list 'aref x 12)) -(defmacro tar-header-dmin (x) (list 'aref x 13)) - -(defmacro make-tar-desc (data-start tokens) - (list 'cons data-start tokens)) - -(defmacro tar-desc-data-start (x) (list 'car x)) -(defmacro tar-desc-tokens (x) (list 'cdr x)) +(defstruct (tar-header + (:constructor nil) + (:type vector) + :named + (:constructor + make-tar-header (data-start name mode uid gid size date checksum + link-type link-name magic uname gname dmaj dmin))) + data-start name mode uid gid size date checksum link-type link-name + magic uname gname dmaj dmin + ;; Start of the header can be nil (meaning it's 512 bytes before data-start) + ;; or a marker (in case the header uses LongLink thingies). + header-start) (defconst tar-name-offset 0) (defconst tar-mode-offset (+ tar-name-offset 100)) @@ -197,68 +215,118 @@ This information is useful, but it takes screen space away from file names." (defconst tar-gname-offset (+ tar-uname-offset 32)) (defconst tar-dmaj-offset (+ tar-gname-offset 32)) (defconst tar-dmin-offset (+ tar-dmaj-offset 8)) -(defconst tar-end-offset (+ tar-dmin-offset 8)) - -(defun tar-header-block-tokenize (string) +(defconst tar-prefix-offset (+ tar-dmin-offset 8)) +(defconst tar-end-offset (+ tar-prefix-offset 155)) + +(defun tar-roundup-512 (s) + "Round S up to the next multiple of 512." + (ash (ash (+ s 511) -9) 9)) + +(defun tar-header-block-tokenize (pos coding) "Return a `tar-header' structure. This is a list of name, mode, uid, gid, size, write-date, checksum, link-type, and link-name." - (cond ((< (length string) 512) nil) - (;(some 'plusp string) ; <-- oops, massive cycle hog! - (or (not (= 0 (aref string 0))) ; This will do. - (not (= 0 (aref string 101)))) - (let* ((name-end (1- tar-mode-offset)) - (link-end (1- tar-magic-offset)) - (uname-end (1- tar-gname-offset)) - (gname-end (1- tar-dmaj-offset)) - (link-p (aref string tar-linkp-offset)) - (magic-str (substring string tar-magic-offset (1- tar-uname-offset))) - (uname-valid-p (or (string= "ustar " magic-str) (string= "GNUtar " magic-str))) - name linkname - (nulsexp "[^\000]*\000")) - (when (string-match nulsexp string tar-name-offset) - (setq name-end (min name-end (1- (match-end 0))))) - (when (string-match nulsexp string tar-link-offset) - (setq link-end (min link-end (1- (match-end 0))))) - (when (string-match nulsexp string tar-uname-offset) - (setq uname-end (min uname-end (1- (match-end 0))))) - (when (string-match nulsexp string tar-gname-offset) - (setq gname-end (min gname-end (1- (match-end 0))))) - (setq name (substring string tar-name-offset name-end) - link-p (if (or (= link-p 0) (= link-p ?0)) - nil - (- link-p ?0))) - (setq linkname (substring string tar-link-offset link-end)) - (if default-enable-multibyte-characters - (setq name - (decode-coding-string name - (or file-name-coding-system - default-file-name-coding-system - 'undecided)) - linkname - (decode-coding-string linkname - (or file-name-coding-system - default-file-name-coding-system - 'undecided)))) - (if (and (null link-p) (string-match "/\\'" name)) (setq link-p 5)) ; directory - (make-tar-header - name - (tar-parse-octal-integer string tar-mode-offset tar-uid-offset) - (tar-parse-octal-integer string tar-uid-offset tar-gid-offset) - (tar-parse-octal-integer string tar-gid-offset tar-size-offset) - (tar-parse-octal-integer string tar-size-offset tar-time-offset) - (tar-parse-octal-long-integer string tar-time-offset tar-chk-offset) - (tar-parse-octal-integer string tar-chk-offset tar-linkp-offset) - link-p - linkname - uname-valid-p - (and uname-valid-p (substring string tar-uname-offset uname-end)) - (and uname-valid-p (substring string tar-gname-offset gname-end)) - (tar-parse-octal-integer string tar-dmaj-offset tar-dmin-offset) - (tar-parse-octal-integer string tar-dmin-offset tar-end-offset) - ))) - (t 'empty-tar-block))) - + (if (> (+ pos 512) (point-max)) (error "Malformed Tar header")) + (assert (zerop (mod (- pos (point-min)) 512))) + (assert (not enable-multibyte-characters)) + (let ((string (buffer-substring pos (setq pos (+ pos 512))))) + (when ;(some 'plusp string) ; <-- oops, massive cycle hog! + (or (not (= 0 (aref string 0))) ; This will do. + (not (= 0 (aref string 101)))) + (let* ((name-end tar-mode-offset) + (link-end (1- tar-magic-offset)) + (uname-end (1- tar-gname-offset)) + (gname-end (1- tar-dmaj-offset)) + (link-p (aref string tar-linkp-offset)) + (magic-str (substring string tar-magic-offset + ;; The magic string is actually 6bytes + ;; of magic string plus 2bytes of version + ;; which we here ignore. + (- tar-uname-offset 2))) + ;; The magic string is "ustar\0" for POSIX format, and + ;; "ustar " for GNU Tar's format. + (uname-valid-p (car (member magic-str '("ustar " "ustar\0")))) + name linkname + (nulsexp "[^\000]*\000")) + (when (string-match nulsexp string tar-name-offset) + (setq name-end (min name-end (1- (match-end 0))))) + (when (string-match nulsexp string tar-link-offset) + (setq link-end (min link-end (1- (match-end 0))))) + (when (string-match nulsexp string tar-uname-offset) + (setq uname-end (min uname-end (1- (match-end 0))))) + (when (string-match nulsexp string tar-gname-offset) + (setq gname-end (min gname-end (1- (match-end 0))))) + (setq name (substring string tar-name-offset name-end) + link-p (if (or (= link-p 0) (= link-p ?0)) + nil + (- link-p ?0))) + (setq linkname (substring string tar-link-offset link-end)) + (when (and (equal uname-valid-p "ustar\0") + (string-match nulsexp string tar-prefix-offset) + (> (match-end 0) (1+ tar-prefix-offset))) + (setq name (concat (substring string tar-prefix-offset + (1- (match-end 0))) + "/" name))) + (if default-enable-multibyte-characters + (setq name + (decode-coding-string name coding) + linkname + (decode-coding-string linkname coding))) + (if (and (null link-p) (string-match "/\\'" name)) + (setq link-p 5)) ; directory + + (if (and (equal name "././@LongLink") + (equal magic-str "ustar ")) ;OLDGNU_MAGIC. + ;; This is a GNU Tar long-file-name header. + (let* ((size (tar-parse-octal-integer + string tar-size-offset tar-time-offset)) + ;; -1 so as to strip the terminating 0 byte. + (name (buffer-substring pos (+ pos size -1))) + (descriptor (tar-header-block-tokenize + (+ pos (tar-roundup-512 size)) + coding))) + (cond + ((eq link-p (- ?L ?0)) ;GNUTYPE_LONGNAME. + (setf (tar-header-name descriptor) name)) + ((eq link-p (- ?K ?0)) ;GNUTYPE_LONGLINK. + (setf (tar-header-link-name descriptor) name)) + (t + (message "Unrecognized GNU Tar @LongLink format"))) + (setf (tar-header-header-start descriptor) + (copy-marker (- pos 512) t)) + descriptor) + + (make-tar-header + (copy-marker pos nil) + name + (tar-parse-octal-integer string tar-mode-offset tar-uid-offset) + (tar-parse-octal-integer string tar-uid-offset tar-gid-offset) + (tar-parse-octal-integer string tar-gid-offset tar-size-offset) + (tar-parse-octal-integer string tar-size-offset tar-time-offset) + (tar-parse-octal-long-integer string tar-time-offset tar-chk-offset) + (tar-parse-octal-integer string tar-chk-offset tar-linkp-offset) + link-p + linkname + uname-valid-p + (and uname-valid-p (substring string tar-uname-offset uname-end)) + (and uname-valid-p (substring string tar-gname-offset gname-end)) + (tar-parse-octal-integer string tar-dmaj-offset tar-dmin-offset) + (tar-parse-octal-integer string tar-dmin-offset tar-prefix-offset) + )))))) + +;; Pseudo-field. +(defun tar-header-data-end (descriptor) + (let* ((data-start (tar-header-data-start descriptor)) + (link-type (tar-header-link-type descriptor)) + (size (tar-header-size descriptor)) + (fudge (cond + ;; Foo. There's an extra empty block after these. + ((memq link-type '(20 55)) 512) + (t 0)))) + (+ data-start fudge + (if (and (null link-type) (> size 0)) + (tar-roundup-512 size) + 0)))) (defun tar-parse-octal-integer (string &optional start end) (if (null start) (setq start 0)) @@ -298,6 +366,7 @@ write-date, checksum, link-type, and link-name." (defun tar-header-block-checksum (string) "Compute and return a tar-acceptable checksum for this block." + (assert (not (multibyte-string-p string))) (let* ((chk-field-start tar-chk-offset) (chk-field-end (+ chk-field-start 8)) (sum 0) @@ -349,7 +418,7 @@ MODE should be an integer which is a file mode value." ;; (ck (tar-header-checksum tar-hblock)) (type (tar-header-link-type tar-hblock)) (link-name (tar-header-link-name tar-hblock))) - (format "%c%c%s%8s/%-8s%7s%s %s%s" + (format "%c%c%s %7s/%-7s %7s%s %s%s" (if mod-p ?* ? ) (cond ((or (eq type nil) (eq type 0)) ?-) ((eq type 1) ?h) ; link @@ -381,91 +450,68 @@ MODE should be an integer which is a file mode value." (defun tar-untar-buffer () "Extract all archive members in the tar-file into the current directory." (interactive) - (let ((multibyte enable-multibyte-characters)) - (unwind-protect - (save-restriction - (widen) - (set-buffer-multibyte nil) - (dolist (descriptor tar-parse-info) - (let* ((tokens (tar-desc-tokens descriptor)) - (name (tar-header-name tokens)) - (dir (file-name-directory name)) - (start (+ (tar-desc-data-start descriptor) - (- tar-header-offset (point-min)))) - (end (+ start (tar-header-size tokens)))) - (unless (file-directory-p name) - (message "Extracting %s" name) - (if (and dir (not (file-exists-p dir))) - (make-directory dir t)) - (unless (file-directory-p name) - (write-region start end name)) - (set-file-modes name (tar-header-mode tokens)))))) - (set-buffer-multibyte multibyte)))) + ;; FIXME: make it work even if we're not in tar-mode. + (let ((descriptors tar-parse-info)) ;Read the var in its buffer. + (with-current-buffer + (if (tar-data-swapped-p) tar-data-buffer (current-buffer)) + (set-buffer-multibyte nil) ;Hopefully, a no-op. + (dolist (descriptor descriptors) + (let* ((name (tar-header-name descriptor)) + (dir (if (eq (tar-header-link-type descriptor) 5) + name + (file-name-directory name))) + (start (tar-header-data-start descriptor)) + (end (+ start (tar-header-size descriptor)))) + (unless (file-directory-p name) + (message "Extracting %s" name) + (if (and dir (not (file-exists-p dir))) + (make-directory dir t)) + (unless (file-directory-p name) + (write-region start end name)) + (set-file-modes name (tar-header-mode descriptor)))))))) (defun tar-summarize-buffer () - "Parse the contents of the tar file in the current buffer. -Place a dired-like listing on the front; -then narrow to it, so that only that listing -is visible (and the real data of the buffer is hidden)." - (let ((modified (buffer-modified-p))) - (set-buffer-multibyte nil) - (let* ((result '()) - (pos (point-min)) - (progress-reporter + "Parse the contents of the tar file in the current buffer." + (assert (tar-data-swapped-p)) + (let* ((modified (buffer-modified-p)) + (result '()) + (pos (point-min)) + (coding tar-file-name-coding-system) + (progress-reporter + (with-current-buffer tar-data-buffer (make-progress-reporter "Parsing tar file..." - (point-min) (max 1 (- (buffer-size) 1024)))) - tokens) - (while (and (<= (+ pos 512) (point-max)) - (not (eq 'empty-tar-block - (setq tokens - (tar-header-block-tokenize - (buffer-substring pos (+ pos 512))))))) - (setq pos (+ pos 512)) - (progress-reporter-update progress-reporter pos) - (if (memq (tar-header-link-type tokens) '(20 55)) - ;; Foo. There's an extra empty block after these. - (setq pos (+ pos 512))) - (let ((size (tar-header-size tokens))) + (point-min) (point-max)))) + descriptor) + (with-current-buffer tar-data-buffer + (while (and (< pos (point-max)) + (setq descriptor (tar-header-block-tokenize pos coding))) + (let ((size (tar-header-size descriptor))) (if (< size 0) (error "%s has size %s - corrupted" - (tar-header-name tokens) size)) - ; - ; This is just too slow. Don't really need it anyway.... - ;(tar-header-block-check-checksum - ; hblock (tar-header-block-checksum hblock) - ; (tar-header-name tokens)) - - (push (make-tar-desc pos tokens) result) - - (and (null (tar-header-link-type tokens)) - (> size 0) - (setq pos - (+ pos 512 (ash (ash (1- size) -9) 9)) ; this works - ;(+ pos (+ size (- 512 (rem (1- size) 512)))) ; this doesn't - )))) - (make-local-variable 'tar-parse-info) - (setq tar-parse-info (nreverse result)) - ;; A tar file should end with a block or two of nulls, - ;; but let's not get a fatal error if it doesn't. - (if (eq tokens 'empty-tar-block) - (progress-reporter-done progress-reporter) - (message "Warning: premature EOF parsing tar file"))) - (set-buffer-multibyte default-enable-multibyte-characters) + (tar-header-name descriptor) size))) + ;; + ;; This is just too slow. Don't really need it anyway.... + ;;(tar-header-block-check-checksum + ;; hblock (tar-header-block-checksum hblock) + ;; (tar-header-name descriptor)) + + (push descriptor result) + (setq pos (tar-header-data-end descriptor)) + (progress-reporter-update progress-reporter pos))) + + (set (make-local-variable 'tar-parse-info) (nreverse result)) + ;; A tar file should end with a block or two of nulls, + ;; but let's not get a fatal error if it doesn't. + (if (null descriptor) + (progress-reporter-done progress-reporter) + (message "Warning: premature EOF parsing tar file")) (goto-char (point-min)) - (let ((inhibit-read-only t)) - ;; Collect summary lines and insert them all at once since tar files - ;; can be pretty big. - (let ((total-summaries - (mapconcat - (lambda (tar-desc) - (tar-header-block-summarize (tar-desc-tokens tar-desc))) - tar-parse-info - "\n"))) - (insert total-summaries "\n")) - (narrow-to-region (point-min) (point)) - (set (make-local-variable 'tar-header-offset) (position-bytes (point))) - (goto-char (point-min)) - (restore-buffer-modified-p modified)))) + (let ((inhibit-read-only t) + (total-summaries + (mapconcat 'tar-header-block-summarize tar-parse-info "\n"))) + (insert total-summaries "\n")) + (goto-char (point-min)) + (restore-buffer-modified-p modified))) (defvar tar-mode-map (let ((map (make-keymap))) @@ -497,6 +543,8 @@ is visible (and the real data of the buffer is hidden)." (define-key map "M" 'tar-chmod-entry) (define-key map "G" 'tar-chgrp-entry) (define-key map "O" 'tar-chown-entry) + ;; Let mouse-1 follow the link. + (define-key map [follow-link] 'mouse-face) ;; Make menu bar items. @@ -540,7 +588,7 @@ is visible (and the real data of the buffer is hidden)." '("Copy to..." . tar-copy)) (define-key map [menu-bar operate expunge] '("Expunge Marked Files" . tar-expunge)) - + map) "Local keymap for Tar mode listings.") @@ -549,6 +597,15 @@ is visible (and the real data of the buffer is hidden)." (put 'tar-mode 'mode-class 'special) (put 'tar-subfile-mode 'mode-class 'special) +(defun tar-change-major-mode-hook () + ;; Bring the actual Tar data back into the main buffer. + (when (tar-data-swapped-p) (tar-swap-data)) + ;; Throw away the summary. + (when (buffer-live-p tar-data-buffer) (kill-buffer tar-data-buffer))) + +(defun tar-mode-kill-buffer-hook () + (if (buffer-live-p tar-data-buffer) (kill-buffer tar-data-buffer))) + ;;;###autoload (define-derived-mode tar-mode nil "Tar" "Major mode for viewing a tar file as a dired-like listing of its contents. @@ -565,26 +622,45 @@ inside of a tar archive without extracting it and re-archiving it. See also: variables `tar-update-datestamp' and `tar-anal-blocksize'. \\{tar-mode-map}" - ;; this is not interactive because you shouldn't be turning this - ;; mode on and off. You can corrupt things that way. - ;; rms: with permanent locals, it should now be possible to make this work - ;; interactively in some reasonable fashion. - (make-local-variable 'tar-header-offset) (make-local-variable 'tar-parse-info) (set (make-local-variable 'require-final-newline) nil) ; binary data, dude... - (set (make-local-variable 'revert-buffer-function) 'tar-mode-revert) (set (make-local-variable 'local-enable-local-variables) nil) (set (make-local-variable 'next-line-add-newlines) nil) + (set (make-local-variable 'tar-file-name-coding-system) + (or file-name-coding-system + default-file-name-coding-system + locale-coding-system)) ;; Prevent loss of data when saving the file. (set (make-local-variable 'file-precious-flag) t) - (auto-save-mode 0) - (set (make-local-variable 'write-contents-functions) '(tar-mode-write-file)) (buffer-disable-undo) (widen) - (if (and (boundp 'tar-header-offset) tar-header-offset) - (narrow-to-region (point-min) (byte-to-position tar-header-offset)) - (tar-summarize-buffer) - (tar-next-line 0))) + ;; Now move the Tar data into an auxiliary buffer, so we can use the main + ;; buffer for the summary. + (assert (not (tar-data-swapped-p))) + (set (make-local-variable 'revert-buffer-function) 'tar-mode-revert) + ;; We started using write-contents-functions, but this hook is not + ;; used during auto-save, so we now use + ;; write-region-annotate-functions which hooks at a lower-level. + (add-hook 'write-region-annotate-functions 'tar-write-region-annotate nil t) + (add-hook 'kill-buffer-hook 'tar-mode-kill-buffer-hook nil t) + (add-hook 'change-major-mode-hook 'tar-change-major-mode-hook nil t) + ;; Tar data is made of bytes, not chars. + (set-buffer-multibyte nil) ;Hopefully a no-op. + (set (make-local-variable 'tar-data-buffer) + (generate-new-buffer (format " *tar-data %s*" + (file-name-nondirectory + (or buffer-file-name (buffer-name)))))) + (condition-case err + (progn + (tar-swap-data) + (tar-summarize-buffer) + (tar-next-line 0)) + (error + ;; If summarizing caused an error, then maybe the buffer doesn't contain + ;; tar data. Rather than show a mysterious empty buffer, let's + ;; revert to fundamental-mode. + (fundamental-mode) + (signal (car err) (cdr err))))) (defun tar-subfile-mode (p) @@ -616,26 +692,23 @@ appear on disk when you save the tar-file's buffer." ;; Revert the buffer and recompute the dired-like listing. (defun tar-mode-revert (&optional no-auto-save no-confirm) - (let ((revert-buffer-function nil) - (old-offset tar-header-offset) - success) - (setq tar-header-offset nil) - (unwind-protect - (and (revert-buffer t no-confirm) - (progn (widen) - (setq success t) - (tar-mode))) - ;; If the revert was canceled, - ;; put back the old value of tar-header-offset. - (or success - (setq tar-header-offset old-offset))))) + (unwind-protect + (let ((revert-buffer-function nil)) + (if (tar-data-swapped-p) (tar-swap-data)) + ;; FIXME: If we ask for confirmation, the user will be temporarily + ;; looking at the raw data. + (revert-buffer no-auto-save no-confirm 'preserve-modes) + ;; Recompute the summary. + (if (buffer-live-p tar-data-buffer) (kill-buffer tar-data-buffer)) + (tar-mode)) + (unless (tar-data-swapped-p) (tar-swap-data)))) (defun tar-next-line (arg) "Move cursor vertically down ARG lines and to the start of the filename." (interactive "p") (forward-line arg) - (if (eobp) nil (forward-char (if tar-mode-show-date 54 36)))) + (goto-char (or (next-single-property-change (point) 'mouse-face) (point)))) (defun tar-previous-line (arg) "Move cursor vertically up ARG lines and to the start of the filename." @@ -645,8 +718,7 @@ appear on disk when you save the tar-file's buffer." (defun tar-current-descriptor (&optional noerror) "Return the tar-descriptor of the current line, or signals an error." ;; I wish lines had plists, like in ZMACS... - (or (nth (count-lines (point-min) - (save-excursion (beginning-of-line) (point))) + (or (nth (count-lines (point-min) (line-beginning-position)) tar-parse-info) (if noerror nil @@ -654,9 +726,8 @@ appear on disk when you save the tar-file's buffer." (defun tar-get-descriptor () (let* ((descriptor (tar-current-descriptor)) - (tokens (tar-desc-tokens descriptor)) - (size (tar-header-size tokens)) - (link-p (tar-header-link-type tokens))) + (size (tar-header-size descriptor)) + (link-p (tar-header-link-type descriptor))) (if link-p (error "This is %s, not a real file" (cond ((eq link-p 5) "a directory") @@ -673,8 +744,7 @@ appear on disk when you save the tar-file's buffer." (defun tar-mouse-extract (event) "Extract a file whose tar directory line you click on." (interactive "e") - (save-excursion - (set-buffer (window-buffer (posn-window (event-end event)))) + (with-current-buffer (window-buffer (posn-window (event-end event))) (save-excursion (goto-char (posn-point (event-end event))) ;; Just make sure this doesn't get an error. @@ -694,14 +764,11 @@ appear on disk when you save the tar-file's buffer." (interactive) (let* ((view-p (eq other-window-p 'view)) (descriptor (tar-get-descriptor)) - (tokens (tar-desc-tokens descriptor)) - (name (tar-header-name tokens)) - (size (tar-header-size tokens)) - (start (+ (tar-desc-data-start descriptor) - (- tar-header-offset (point-min)))) + (name (tar-header-name descriptor)) + (size (tar-header-size descriptor)) + (start (tar-header-data-start descriptor)) (end (+ start size))) (let* ((tar-buffer (current-buffer)) - (tar-buffer-multibyte enable-multibyte-characters) (tarname (buffer-name)) (bufname (concat (file-name-nondirectory name) " (" @@ -710,93 +777,75 @@ appear on disk when you save the tar-file's buffer." (read-only-p (or buffer-read-only view-p)) (new-buffer-file-name (expand-file-name ;; `:' is not allowed on Windows - (concat tarname "!" name))) + (concat tarname "!" + (if (string-match "/" name) + name + ;; Make sure `name' contains a / + ;; so set-auto-mode doesn't try + ;; to look at `tarname' for hints. + (concat "./" name))))) (buffer (get-file-buffer new-buffer-file-name)) - (just-created nil)) + (just-created nil) + undo-list) (unless buffer (setq buffer (generate-new-buffer bufname)) + (with-current-buffer buffer + (setq undo-list buffer-undo-list + buffer-undo-list t)) (setq bufname (buffer-name buffer)) (setq just-created t) - (unwind-protect - (progn - (widen) - (set-buffer-multibyte nil) - (save-excursion - (set-buffer buffer) - (let ((buffer-undo-list t)) - (if enable-multibyte-characters - (progn - ;; We must avoid unibyte->multibyte conversion. - (set-buffer-multibyte nil) - (insert-buffer-substring tar-buffer start end) - (set-buffer-multibyte t)) - (insert-buffer-substring tar-buffer start end)) - (goto-char (point-min)) - (setq buffer-file-name new-buffer-file-name) - (setq buffer-file-truename - (abbreviate-file-name buffer-file-name)) - ;; We need to mimic the parts of insert-file-contents - ;; which determine the coding-system and decode the text. - (let ((coding - (or coding-system-for-read - (and set-auto-coding-function - (save-excursion - (funcall set-auto-coding-function - name (- (point-max) (point))))) - ;; The following binding causes - ;; find-buffer-file-type-coding-system - ;; (defined on dos-w32.el) to act as if - ;; the file being extracted existed, so - ;; that the file's contents' encoding and - ;; EOL format are auto-detected. - (let ((file-name-handler-alist - '(("" . tar-file-name-handler)))) - (car (find-operation-coding-system - 'insert-file-contents - (cons name (current-buffer)) t))))) - (multibyte enable-multibyte-characters) - (detected (detect-coding-region - (point-min) - (min (+ (point-min) 16384) (point-max)) t))) - (if coding - (or (numberp (coding-system-eol-type coding)) - (vectorp (coding-system-eol-type detected)) - (setq coding (coding-system-change-eol-conversion - coding - (coding-system-eol-type detected)))) - (setq coding - (find-new-buffer-file-coding-system detected))) - (if (or (eq coding 'no-conversion) - (eq (coding-system-type coding) 5)) - (setq multibyte (set-buffer-multibyte nil))) - (or multibyte - (setq coding - (coding-system-change-text-conversion - coding 'raw-text))) - (decode-coding-region (point-min) (point-max) coding) - ;; Force buffer-file-coding-system to what - ;; decode-coding-region actually used. - (set-buffer-file-coding-system last-coding-system-used t)) - ;; Set the default-directory to the dir of the - ;; superior buffer. - (setq default-directory - (save-excursion - (set-buffer tar-buffer) - default-directory)) - (normal-mode) ; pick a mode. - (rename-buffer bufname) - (make-local-variable 'tar-superior-buffer) - (make-local-variable 'tar-superior-descriptor) - (setq tar-superior-buffer tar-buffer) - (setq tar-superior-descriptor descriptor) - (setq buffer-read-only read-only-p) - (set-buffer-modified-p nil)) - (tar-subfile-mode 1)) - (set-buffer tar-buffer)) - (narrow-to-region (point-min) tar-header-offset) - (set-buffer-multibyte tar-buffer-multibyte))) + (with-current-buffer tar-data-buffer + (let (coding) + (narrow-to-region start end) + (goto-char start) + (setq coding (or coding-system-for-read + (and set-auto-coding-function + (funcall set-auto-coding-function + name (- end start))) + ;; The following binding causes + ;; find-buffer-file-type-coding-system + ;; (defined on dos-w32.el) to act as if + ;; the file being extracted existed, so + ;; that the file's contents' encoding and + ;; EOL format are auto-detected. + (let ((file-name-handler-alist + '(("" . tar-file-name-handler)))) + (car (find-operation-coding-system + 'insert-file-contents + (cons name (current-buffer)) t))))) + (if (or (not coding) + (eq (coding-system-type coding) 'undecided)) + (setq coding (detect-coding-region start end t))) + (if (and default-enable-multibyte-characters + (coding-system-get coding :for-unibyte)) + (with-current-buffer buffer + (set-buffer-multibyte nil))) + (widen) + (decode-coding-region start end coding buffer))) + (with-current-buffer buffer + (goto-char (point-min)) + (setq buffer-file-name new-buffer-file-name) + (setq buffer-file-truename + (abbreviate-file-name buffer-file-name)) + ;; Force buffer-file-coding-system to what + ;; decode-coding-region actually used. + (set-buffer-file-coding-system last-coding-system-used t) + ;; Set the default-directory to the dir of the + ;; superior buffer. + (setq default-directory + (with-current-buffer tar-buffer + default-directory)) + (rename-buffer bufname) + (set-buffer-modified-p nil) + (setq buffer-undo-list undo-list) + (normal-mode) ; pick a mode. + (set (make-local-variable 'tar-superior-buffer) tar-buffer) + (set (make-local-variable 'tar-superior-descriptor) descriptor) + (setq buffer-read-only read-only-p) + (tar-subfile-mode 1))) (if view-p - (view-buffer buffer (and just-created 'kill-buffer)) + (view-buffer + buffer (and just-created 'kill-buffer-if-not-modified)) (if (eq other-window-p 'display) (display-buffer buffer) (if other-window-p @@ -824,8 +873,7 @@ appear on disk when you save the tar-file's buffer." "Read a file name with this line's entry as the default." (or prompt (setq prompt "Copy to: ")) (let* ((default-file (expand-file-name - (tar-header-name (tar-desc-tokens - (tar-current-descriptor))))) + (tar-header-name (tar-current-descriptor)))) (target (expand-file-name (read-file-name prompt (file-name-directory default-file) @@ -846,13 +894,10 @@ If TO-FILE is not supplied, it is prompted for, defaulting to the name of the current tar-entry." (interactive (list (tar-read-file-name))) (let* ((descriptor (tar-get-descriptor)) - (tokens (tar-desc-tokens descriptor)) - (name (tar-header-name tokens)) - (size (tar-header-size tokens)) - (start (+ (tar-desc-data-start descriptor) - (- tar-header-offset (point-min)))) + (name (tar-header-name descriptor)) + (size (tar-header-size descriptor)) + (start (tar-header-data-start descriptor)) (end (+ start size)) - (multibyte enable-multibyte-characters) (inhibit-file-name-handlers inhibit-file-name-handlers) (inhibit-file-name-operation inhibit-file-name-operation)) (save-restriction @@ -866,11 +911,8 @@ the current tar-entry." (and (eq inhibit-file-name-operation 'write-region) inhibit-file-name-handlers)) inhibit-file-name-operation 'write-region)) - (unwind-protect - (let ((coding-system-for-write 'no-conversion)) - (set-buffer-multibyte nil) - (write-region start end to-file nil nil nil t)) - (set-buffer-multibyte multibyte))) + (let ((coding-system-for-write 'no-conversion)) + (write-region start end to-file nil nil nil t))) (message "Copied tar entry %s to %s" name to-file))) (defun tar-flag-deleted (p &optional unflag) @@ -899,47 +941,21 @@ With a prefix argument, un-mark that many files backward." (tar-flag-deleted (- p) t)) -;; When this function is called, it is sure that the buffer is unibyte. (defun tar-expunge-internal () "Expunge the tar-entry specified by the current line." - (let* ((descriptor (tar-current-descriptor)) - (tokens (tar-desc-tokens descriptor)) - ;; (line (tar-desc-data-start descriptor)) - (name (tar-header-name tokens)) - (size (tar-header-size tokens)) - (link-p (tar-header-link-type tokens)) - (start (tar-desc-data-start descriptor)) - (following-descs (cdr (memq descriptor tar-parse-info)))) - (if link-p (setq size 0)) ; size lies for hard-links. + (let ((descriptor (tar-current-descriptor))) ;; ;; delete the current line... - (beginning-of-line) - (let ((line-start (point))) - (end-of-line) (forward-char) - ;; decrement the header-pointer to be in sync... - (setq tar-header-offset (- tar-header-offset (- (point) line-start))) - (delete-region line-start (point))) + (delete-region (line-beginning-position) (line-beginning-position 2)) ;; ;; delete the data pointer... (setq tar-parse-info (delq descriptor tar-parse-info)) ;; ;; delete the data from inside the file... - (widen) - (let* ((data-start (+ start (- tar-header-offset (point-min)) -512)) - (data-end (+ data-start 512 (ash (ash (+ size 511) -9) 9)))) - (delete-region data-start data-end) - ;; - ;; and finally, decrement the start-pointers of all following - ;; entries in the archive. This is a pig when deleting a bunch - ;; of files at once - we could optimize this to only do the - ;; iteration over the files that remain, or only iterate up to - ;; the next file to be deleted. - (let ((data-length (- data-end data-start))) - (dolist (desc following-descs) - (tar-setf (tar-desc-data-start desc) - (- (tar-desc-data-start desc) data-length)))) - )) - (narrow-to-region (point-min) tar-header-offset)) + (with-current-buffer tar-data-buffer + (delete-region (or (tar-header-header-start descriptor) + (- (tar-header-data-start descriptor) 512)) + (tar-header-data-end descriptor))))) (defun tar-expunge (&optional noconfirm) @@ -949,11 +965,8 @@ for this to be permanent." (interactive) (if (or noconfirm (y-or-n-p "Expunge files marked for deletion? ")) - (let ((n 0) - (multibyte enable-multibyte-characters)) + (let ((n 0)) (save-excursion - (widen) - (set-buffer-multibyte nil) (goto-char (point-min)) (while (not (eobp)) (if (looking-at "D") @@ -961,10 +974,7 @@ for this to be permanent." (setq n (1+ n))) (forward-line 1))) ;; after doing the deletions, add any padding that may be necessary. - (tar-pad-to-blocksize) - (widen) - (set-buffer-multibyte multibyte) - (narrow-to-region (point-min) tar-header-offset)) + (tar-pad-to-blocksize)) (if (zerop n) (message "Nothing to expunge.") (message "%s files expunged. Be sure to save this buffer." n))))) @@ -975,7 +985,7 @@ for this to be permanent." (interactive) (save-excursion (goto-char (point-min)) - (while (< (position-bytes (point)) tar-header-offset) + (while (not (eobp)) (if (not (eq (following-char) ?\s)) (progn (delete-char 1) (insert " "))) (forward-line 1)))) @@ -988,23 +998,20 @@ the user id as a string; otherwise, you must edit it as a number. You can force editing as a number by calling this with a prefix arg. This does not modify the disk image; you must save the tar file itself for this to be permanent." - (interactive (list - (let ((tokens (tar-desc-tokens (tar-current-descriptor)))) - (if (or current-prefix-arg - (not (tar-header-magic tokens))) - (let (n) - (while (not (numberp (setq n (read-minibuffer - "New UID number: " - (format "%s" (tar-header-uid tokens))))))) - n) - (read-string "New UID string: " (tar-header-uname tokens)))))) + (interactive + (list + (let ((descriptor (tar-current-descriptor))) + (if (or current-prefix-arg + (not (tar-header-magic descriptor))) + (read-number + "New UID number: " + (format "%s" (tar-header-uid descriptor))) + (read-string "New UID string: " (tar-header-uname descriptor)))))) (cond ((stringp new-uid) - (tar-setf (tar-header-uname (tar-desc-tokens (tar-current-descriptor))) - new-uid) + (setf (tar-header-uname (tar-current-descriptor)) new-uid) (tar-alter-one-field tar-uname-offset (concat new-uid "\000"))) (t - (tar-setf (tar-header-uid (tar-desc-tokens (tar-current-descriptor))) - new-uid) + (setf (tar-header-uid (tar-current-descriptor)) new-uid) (tar-alter-one-field tar-uid-offset (concat (substring (format "%6o" new-uid) 0 6) "\000 "))))) @@ -1016,24 +1023,21 @@ the group id as a string; otherwise, you must edit it as a number. You can force editing as a number by calling this with a prefix arg. This does not modify the disk image; you must save the tar file itself for this to be permanent." - (interactive (list - (let ((tokens (tar-desc-tokens (tar-current-descriptor)))) - (if (or current-prefix-arg - (not (tar-header-magic tokens))) - (let (n) - (while (not (numberp (setq n (read-minibuffer - "New GID number: " - (format "%s" (tar-header-gid tokens))))))) - n) - (read-string "New GID string: " (tar-header-gname tokens)))))) + (interactive + (list + (let ((descriptor (tar-current-descriptor))) + (if (or current-prefix-arg + (not (tar-header-magic descriptor))) + (read-number + "New GID number: " + (format "%s" (tar-header-gid descriptor))) + (read-string "New GID string: " (tar-header-gname descriptor)))))) (cond ((stringp new-gid) - (tar-setf (tar-header-gname (tar-desc-tokens (tar-current-descriptor))) - new-gid) + (setf (tar-header-gname (tar-current-descriptor)) new-gid) (tar-alter-one-field tar-gname-offset (concat new-gid "\000"))) (t - (tar-setf (tar-header-gid (tar-desc-tokens (tar-current-descriptor))) - new-gid) + (setf (tar-header-gid (tar-current-descriptor)) new-gid) (tar-alter-one-field tar-gid-offset (concat (substring (format "%6o" new-gid) 0 6) "\000 "))))) @@ -1043,17 +1047,32 @@ This does not modify the disk image; you must save the tar file itself for this to be permanent." (interactive (list (read-string "New name: " - (tar-header-name (tar-desc-tokens (tar-current-descriptor)))))) + (tar-header-name (tar-current-descriptor))))) (if (string= "" new-name) (error "zero length name")) - (if (> (length new-name) 98) (error "name too long")) - (tar-setf (tar-header-name (tar-desc-tokens (tar-current-descriptor))) - new-name) - (if (multibyte-string-p new-name) - (setq new-name (encode-coding-string new-name - (or file-name-coding-system - default-file-name-coding-system)))) - (tar-alter-one-field 0 - (substring (concat new-name (make-string 99 0)) 0 99))) + (let ((encoded-new-name (encode-coding-string new-name + tar-file-name-coding-system)) + (descriptor (tar-current-descriptor)) + (prefix nil)) + (when (tar-header-header-start descriptor) + ;; FIXME: Make it work for ././@LongLink. + (error "Rename with @LongLink format is not implemented")) + + (when (and (> (length encoded-new-name) 98) + (string-match "/" encoded-new-name + (- (length encoded-new-name) 99)) + (< (match-beginning 0) 155)) + (unless (equal (tar-header-magic descriptor) "ustar\0") + (tar-alter-one-field tar-magic-offset (concat "ustar\0" "00"))) + (setq prefix (substring encoded-new-name 0 (match-beginning 0))) + (setq encoded-new-name (substring encoded-new-name (match-end 0)))) + + (if (> (length encoded-new-name) 98) (error "name too long")) + (setf (tar-header-name descriptor) new-name) + (tar-alter-one-field 0 + (substring (concat encoded-new-name (make-string 99 0)) 0 99)) + (if prefix + (tar-alter-one-field tar-prefix-offset + (substring (concat prefix (make-string 155 0)) 0 155))))) (defun tar-chmod-entry (new-mode) @@ -1062,56 +1081,48 @@ This does not modify the disk image; you must save the tar file itself for this to be permanent." (interactive (list (tar-parse-octal-integer-safe (read-string "New protection (octal): ")))) - (tar-setf (tar-header-mode (tar-desc-tokens (tar-current-descriptor))) - new-mode) + (setf (tar-header-mode (tar-current-descriptor)) new-mode) (tar-alter-one-field tar-mode-offset (concat (substring (format "%6o" new-mode) 0 6) "\000 "))) -(defun tar-alter-one-field (data-position new-data-string) - (let* ((descriptor (tar-current-descriptor)) - (tokens (tar-desc-tokens descriptor)) - (multibyte enable-multibyte-characters)) - (unwind-protect - (save-excursion - ;; - ;; update the header-line. - (beginning-of-line) - (let ((p (point))) - (forward-line 1) - (delete-region p (point)) - (insert (tar-header-block-summarize tokens) "\n") - (setq tar-header-offset (position-bytes (point-max)))) - - (widen) - (set-buffer-multibyte nil) - (let* ((start (+ (tar-desc-data-start descriptor) - (- tar-header-offset (point-min)) - -512))) - ;; - ;; delete the old field and insert a new one. - (goto-char (+ start data-position)) - (delete-region (point) (+ (point) (length new-data-string))) ; <-- - (insert new-data-string) ; <-- - ;; - ;; compute a new checksum and insert it. - (let ((chk (tar-header-block-checksum - (buffer-substring start (+ start 512))))) - (goto-char (+ start tar-chk-offset)) - (delete-region (point) (+ (point) 8)) - (insert (format "%6o" chk)) - (insert 0) - (insert ? ) - (tar-setf (tar-header-checksum tokens) chk) - ;; - ;; ok, make sure we didn't botch it. - (tar-header-block-check-checksum - (buffer-substring start (+ start 512)) - chk (tar-header-name tokens)) - ))) - (narrow-to-region (point-min) tar-header-offset) - (set-buffer-multibyte multibyte) - (tar-next-line 0)))) +(defun tar-alter-one-field (data-position new-data-string &optional descriptor) + (unless descriptor (setq descriptor (tar-current-descriptor))) + ;; + ;; update the header-line. + (let ((col (current-column))) + (delete-region (line-beginning-position) + (prog2 (forward-line 1) + (point) + ;; Insert the new text after the old, before deleting, + ;; to preserve markers such as the window start. + (insert (tar-header-block-summarize descriptor) "\n"))) + (forward-line -1) (move-to-column col)) + + (assert (tar-data-swapped-p)) + (with-current-buffer tar-data-buffer + (let* ((start (- (tar-header-data-start descriptor) 512))) + ;; + ;; delete the old field and insert a new one. + (goto-char (+ start data-position)) + (delete-region (point) (+ (point) (length new-data-string))) ; <-- + (assert (not (or enable-multibyte-characters + (multibyte-string-p new-data-string)))) + (insert new-data-string) + ;; + ;; compute a new checksum and insert it. + (let ((chk (tar-header-block-checksum + (buffer-substring start (+ start 512))))) + (goto-char (+ start tar-chk-offset)) + (delete-region (point) (+ (point) 8)) + (insert (format "%6o\0 " chk)) + (setf (tar-header-checksum descriptor) chk) + ;; + ;; ok, make sure we didn't botch it. + (tar-header-block-check-checksum + (buffer-substring start (+ start 512)) + chk (tar-header-name descriptor)) + )))) (defun tar-octal-time (timeval) @@ -1130,116 +1141,68 @@ This doesn't write anything to disk; you must save the parent tar-file buffer to make your changes permanent." (interactive) (if (not (and (boundp 'tar-superior-buffer) tar-superior-buffer)) - (error "This buffer has no superior tar file buffer")) + (error "This buffer has no superior tar file buffer")) (if (not (and (boundp 'tar-superior-descriptor) tar-superior-descriptor)) - (error "This buffer doesn't have an index into its superior tar file!")) - (save-excursion + (error "This buffer doesn't have an index into its superior tar file!")) (let ((subfile (current-buffer)) - (subfile-multibyte enable-multibyte-characters) - (coding buffer-file-coding-system) - (descriptor tar-superior-descriptor) - subfile-size) - ;; We must make the current buffer unibyte temporarily to avoid - ;; multibyte->unibyte conversion in `insert-buffer-substring'. - (set-buffer-multibyte nil) - (setq subfile-size (buffer-size)) - (set-buffer tar-superior-buffer) - (let* ((tokens (tar-desc-tokens descriptor)) - (start (tar-desc-data-start descriptor)) - (name (tar-header-name tokens)) - (size (tar-header-size tokens)) - (size-pad (ash (ash (+ size 511) -9) 9)) - (head (memq descriptor tar-parse-info)) - (following-descs (cdr head)) - (tar-buffer-multibyte enable-multibyte-characters)) - (if (not head) - (error "Can't find this tar file entry in its parent tar file!")) - (unwind-protect - (save-excursion - (widen) - (set-buffer-multibyte nil) - ;; delete the old data... - (let* ((data-start (+ start (- tar-header-offset (point-min)))) - (data-end (+ data-start (ash (ash (+ size 511) -9) 9)))) - (delete-region data-start data-end) - ;; insert the new data... - (goto-char data-start) - (insert-buffer-substring subfile) - (setq subfile-size - (encode-coding-region - data-start (+ data-start subfile-size) coding)) + (coding buffer-file-coding-system) + (descriptor tar-superior-descriptor) + subfile-size) + (with-current-buffer tar-superior-buffer + (let* ((start (tar-header-data-start descriptor)) + (name (tar-header-name descriptor)) + (size (tar-header-size descriptor)) + (head (memq descriptor tar-parse-info))) + (if (not head) + (error "Can't find this tar file entry in its parent tar file!")) + (with-current-buffer tar-data-buffer + ;; delete the old data... + (let* ((data-start start) + (data-end (+ data-start (tar-roundup-512 size)))) + (narrow-to-region data-start data-end) + (delete-region (point-min) (point-max)) + ;; insert the new data... + (goto-char data-start) + (let ((dest (current-buffer))) + (with-current-buffer subfile + (save-restriction + (widen) + (encode-coding-region (point-min) (point-max) coding dest)))) + (setq subfile-size (- (point-max) (point-min))) + ;; + ;; pad the new data out to a multiple of 512... + (let ((subfile-size-pad (tar-roundup-512 subfile-size))) + (goto-char (point-max)) + (insert (make-string (- subfile-size-pad subfile-size) 0)) + ;; + ;; update the data of this files... + (setf (tar-header-size descriptor) subfile-size) + ;; + ;; Update the size field in the header block. + (widen)))) + ;; + ;; alter the descriptor-line and header + ;; + (let ((position (- (length tar-parse-info) (length head)))) + (goto-char (point-min)) + (forward-line position) + (tar-alter-one-field tar-size-offset (format "%11o " subfile-size)) ;; - ;; pad the new data out to a multiple of 512... - (let ((subfile-size-pad (ash (ash (+ subfile-size 511) -9) 9))) - (goto-char (+ data-start subfile-size)) - (insert (make-string (- subfile-size-pad subfile-size) 0)) - ;; - ;; update the data pointer of this and all following files... - (tar-setf (tar-header-size tokens) subfile-size) - (let ((difference (- subfile-size-pad size-pad))) - (dolist (desc following-descs) - (tar-setf (tar-desc-data-start desc) - (+ (tar-desc-data-start desc) difference)))) - ;; - ;; Update the size field in the header block. - (let ((header-start (- data-start 512))) - (goto-char (+ header-start tar-size-offset)) - (delete-region (point) (+ (point) 12)) - (insert (format "%11o" subfile-size)) - (insert ? ) - ;; - ;; Maybe update the datestamp. - (if (not tar-update-datestamp) - nil - (goto-char (+ header-start tar-time-offset)) - (delete-region (point) (+ (point) 12)) - (insert (tar-octal-time (current-time))) - (insert ? )) - ;; - ;; compute a new checksum and insert it. - (let ((chk (tar-header-block-checksum - (buffer-substring header-start data-start)))) - (goto-char (+ header-start tar-chk-offset)) - (delete-region (point) (+ (point) 8)) - (insert (format "%6o" chk)) - (insert 0) - (insert ? ) - (tar-setf (tar-header-checksum tokens) chk))) - ;; - ;; alter the descriptor-line... - ;; - (let ((position (- (length tar-parse-info) (length head)))) - (goto-char (point-min)) - (forward-line position) - (beginning-of-line) - (let ((p (point)) - after - (m (set-marker (make-marker) tar-header-offset))) - (forward-line 1) - (setq after (point)) - ;; Insert the new text after the old, before deleting, - ;; to preserve the window start. - (let ((line (tar-header-block-summarize tokens t))) - (insert-before-markers (string-as-unibyte line) "\n")) - (delete-region p after) - (setq tar-header-offset (marker-position m))) - ))) - ;; after doing the insertion, add any final padding that may be necessary. - (tar-pad-to-blocksize)) - (narrow-to-region (point-min) tar-header-offset) - (set-buffer-multibyte tar-buffer-multibyte))) - (set-buffer-modified-p t) ; mark the tar file as modified - (tar-next-line 0) - (set-buffer subfile) - ;; Restore the buffer multibyteness. - (set-buffer-multibyte subfile-multibyte) - (set-buffer-modified-p nil) ; mark the tar subfile as unmodified + ;; Maybe update the datestamp. + (when tar-update-datestamp + (tar-alter-one-field tar-time-offset + (concat (tar-octal-time (current-time)) " ")))) + ;; After doing the insertion, add any necessary final padding. + (tar-pad-to-blocksize)) + (set-buffer-modified-p t) ; mark the tar file as modified + (tar-next-line 0)) + (set-buffer-modified-p nil) ; mark the tar subfile as unmodified (message "Saved into tar-buffer `%s'. Be sure to save that buffer!" - (buffer-name tar-superior-buffer)) + (buffer-name tar-superior-buffer)) ;; Prevent basic-save-buffer from changing our coding-system. (setq last-coding-system-used buffer-file-coding-system) ;; Prevent ordinary saving from happening. - t))) + t)) ;; When this function is called, it is sure that the buffer is unibyte. @@ -1248,49 +1211,34 @@ to make your changes permanent." Leaves the region wide." (if (null tar-anal-blocksize) nil - (widen) (let* ((last-desc (nth (1- (length tar-parse-info)) tar-parse-info)) - (start (tar-desc-data-start last-desc)) - (tokens (tar-desc-tokens last-desc)) - (link-p (tar-header-link-type tokens)) - (size (if link-p 0 (tar-header-size tokens))) + (start (tar-header-data-start last-desc)) + (link-p (tar-header-link-type last-desc)) + (size (if link-p 0 (tar-header-size last-desc))) (data-end (+ start size)) (bbytes (ash tar-anal-blocksize 9)) - (pad-to (+ bbytes (* bbytes (/ (- data-end (point-min)) bbytes)))) - (inhibit-read-only t) ; ## - ) + (pad-to (+ bbytes (* bbytes (/ (- data-end (point-min)) bbytes))))) ;; If the padding after the last data is too long, delete some; ;; else insert some until we are padded out to the right number of blocks. ;; - (let ((goal-end (+ (or tar-header-offset 0) pad-to))) - (if (> (point-max) goal-end) - (delete-region goal-end (point-max)) - (goto-char (point-max)) - (insert (make-string (- goal-end (point-max)) ?\0))))))) + (with-current-buffer tar-data-buffer + (let ((goal-end (+ (point-min) pad-to))) + (if (> (point-max) goal-end) + (delete-region goal-end (point-max)) + (goto-char (point-max)) + (insert (make-string (- goal-end (point-max)) ?\0)))))))) + + +;; Used in write-region-annotate-functions to write tar-files out correctly. +(defun tar-write-region-annotate (start end) + ;; When called from write-file (and auto-save), `start' is nil. + ;; When called from M-x write-region, we assume the user wants to save + ;; (part of) the summary, not the tar data. + (unless (or start (not (tar-data-swapped-p))) + (tar-clear-modification-flags) + (set-buffer tar-data-buffer) + nil)) - -;; Used in write-file-hook to write tar-files out correctly. -(defun tar-mode-write-file () - (unwind-protect - (save-excursion - (widen) - ;; Doing this here confuses things - the region gets left too wide! - ;; I suppose this is run in a context where changing the buffer is bad. - ;; (tar-pad-to-blocksize) - ;; tar-header-offset turns out to be null for files fetched with W3, - ;; at least. - (let ((coding-system-for-write 'no-conversion)) - (write-region (if tar-header-offset - (byte-to-position tar-header-offset) - (point-min)) - (point-max) - buffer-file-name nil t)) - (tar-clear-modification-flags) - (set-buffer-modified-p nil)) - (narrow-to-region (point-min) (byte-to-position tar-header-offset))) - ;; Return t because we've written the file. - t) - (provide 'tar-mode) ;; arch-tag: 8a585a4a-340e-42c2-89e7-d3b1013a4b78