]> code.delx.au - gnu-emacs/blobdiff - lisp/tar-mode.el
Fix the prefix action of shr-copy-url
[gnu-emacs] / lisp / tar-mode.el
index 7335a7ff527da726023253dc9365511cce8919e9..0520369511d6d75bf615703ac919608be33059ea 100644 (file)
@@ -1,11 +1,9 @@
-;;; tar-mode.el --- simple editing of tar files from GNU emacs
+;;; 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, 2008, 2009, 2010, 2011
-;;   Free Software Foundation, Inc.
+;; Copyright (C) 1990-1991, 1993-2016 Free Software Foundation, Inc.
 
 ;; Author: Jamie Zawinski <jwz@lucid.com>
-;; Maintainer: FSF
+;; Maintainer: emacs-devel@gnu.org
 ;; Created: 04 Apr 1990
 ;; Keywords: unix
 
@@ -52,9 +50,6 @@
 ;;
 ;; o  chmod should understand "a+x,og-w".
 ;;
-;; o  It's not possible to add a NEW file to a tar archive; not that
-;;    important, but still...
-;;
 ;; o  The code is less efficient that it could be - in a lot of places, I
 ;;    pull a 512-character string out of the buffer and parse it, when I could
 ;;    be parsing it in place, not garbaging a string.  Should redo that.
@@ -99,7 +94,7 @@
 
 ;;; Code:
 
-(eval-when-compile (require 'cl))
+(eval-when-compile (require 'cl-lib))
 
 (defgroup tar nil
   "Simple editing of tar files."
@@ -135,9 +130,10 @@ This information is useful, but it takes screen space away from file names."
   :group 'tar)
 
 (defvar tar-parse-info nil)
-(defvar tar-superior-buffer nil)
-(defvar tar-superior-descriptor nil)
-(defvar tar-subfile-mode nil)
+(defvar tar-superior-buffer nil
+  "Buffer containing the tar archive from which a member was extracted.")
+(defvar tar-superior-descriptor nil
+  "Tar descriptor for a member extracted from an archive.")
 (defvar tar-file-name-coding-system nil)
 
 (put 'tar-superior-buffer 'permanent-local t)
@@ -145,7 +141,7 @@ This information is useful, but it takes screen space away from file names."
 
 ;; 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
+;; want to display may contain non-ascii 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
@@ -171,7 +167,7 @@ This information is useful, but it takes screen space away from file names."
        ;; state correctly: the raw data is expected to be always larger than
        ;; the summary.
        (progn
-        (assert (or (= (buffer-size tar-data-buffer) (buffer-size))
+        (cl-assert (or (= (buffer-size tar-data-buffer) (buffer-size))
                      (eq tar-data-swapped
                          (> (buffer-size tar-data-buffer) (buffer-size)))))
         tar-data-swapped)))
@@ -189,7 +185,7 @@ Preserve the modified states of the buffers and set `buffer-swapped-with'."
 \f
 ;;; down to business.
 
-(defstruct (tar-header
+(cl-defstruct (tar-header
             (:constructor nil)
             (:type vector)
             :named
@@ -223,14 +219,14 @@ Preserve the modified states of the buffers and set `buffer-swapped-with'."
 (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."
   (if (> (+ pos 512) (point-max)) (error "Malformed Tar header"))
-  (assert (zerop (mod (- pos (point-min)) 512)))
-  (assert (not enable-multibyte-characters))
+  (cl-assert (zerop (mod (- pos (point-min)) 512)))
+  (cl-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.
@@ -286,7 +282,7 @@ write-date, checksum, link-type, and link-name."
             (let* ((size (tar-parse-octal-integer
                           string tar-size-offset tar-time-offset))
                    ;; -1 so as to strip the terminating 0 byte.
-                  (name (decode-coding-string 
+                  (name (decode-coding-string
                          (buffer-substring pos (+ pos size -1)) coding))
                    (descriptor (tar-header-block-tokenize
                                 (+ pos (tar-roundup-512 size))
@@ -301,7 +297,7 @@ write-date, checksum, link-type, and link-name."
               (setf (tar-header-header-start descriptor)
                     (copy-marker (- pos 512) t))
               descriptor)
-        
+
           (make-tar-header
            (copy-marker pos nil)
            name
@@ -328,13 +324,10 @@ write-date, checksum, link-type, and link-name."
 (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))
+         (size (tar-header-size descriptor)))
+    (+ data-start
+       ;; Ignore size for files of type 1-6
+       (if (and (not (memq link-type '(1 2 3 4 5 6))) (> size 0))
            (tar-roundup-512 size)
          0))))
 
@@ -373,10 +366,84 @@ write-date, checksum, link-type, and link-name."
        string)
   (tar-parse-octal-integer string))
 
+(defun tar-new-regular-file-header (filename &optional size time)
+  "Return a Tar header for a regular file.
+The header will lack a proper checksum; use `tar-header-block-checksum'
+to compute one, or request `tar-header-serialize' to do that.
+
+Other tar-mode facilities may also require the data-start header
+field to be set to a valid value.
+
+If SIZE is not given or nil, it defaults to 0.
+If TIME is not given or nil, assume now."
+  (make-tar-header
+   nil
+   filename
+   #o644 0 0 (or size 0)
+   (or time (current-time))
+   nil                         ; checksum
+   nil nil
+   nil nil nil nil nil))
+
+(defun tar--pad-to (pos)
+  (make-string (+ pos (- (point)) (point-min)) 0))
+
+(defun tar--put-at (pos val &optional fmt mask)
+  (when val
+    (insert (tar--pad-to pos)
+           (if fmt
+               (format fmt (if mask (logand mask val) val))
+             val))))
+
+(defun tar-header-serialize (header &optional update-checksum)
+  "Return the serialization of a Tar HEADER as a string.
+This function calls `tar-header-block-check-checksum' to ensure the
+checksum is correct.
+
+If UPDATE-CHECKSUM is non-nil, update HEADER with the newly-computed
+checksum before doing the check."
+  (with-temp-buffer
+    (set-buffer-multibyte nil)
+    (let ((encoded-name
+          (encode-coding-string (tar-header-name header)
+                                tar-file-name-coding-system)))
+      (unless (< (length encoded-name) 99)
+       ;; FIXME: Implement it.
+       (error "Long file name support is not implemented"))
+      (insert encoded-name))
+    (tar--put-at tar-mode-offset (tar-header-mode header) "%6o\0 " #o777777)
+    (tar--put-at tar-uid-offset  (tar-header-uid  header) "%6o\0 " #o777777)
+    (tar--put-at tar-gid-offset  (tar-header-gid  header) "%6o\0 " #o777777)
+    (tar--put-at tar-size-offset (tar-header-size header) "%11o ")
+    (insert (tar--pad-to tar-time-offset)
+           (tar-octal-time (tar-header-date header))
+           " ")
+    ;; Omit tar-header-checksum (tar-chk-offset) for now.
+    (tar--put-at   tar-linkp-offset (tar-header-link-type header))
+    (tar--put-at   tar-link-offset  (tar-header-link-name header))
+    (when (tar-header-magic header)
+      (tar--put-at tar-magic-offset (tar-header-magic header))
+      (tar--put-at tar-uname-offset (tar-header-uname header))
+      (tar--put-at tar-gname-offset (tar-header-gname header))
+      (tar--put-at tar-dmaj-offset (tar-header-dmaj header) "%7o\0" #o7777777)
+      (tar--put-at tar-dmin-offset (tar-header-dmin header) "%7o\0" #o7777777))
+    (tar--put-at 512 "")
+    (let ((ck (tar-header-block-checksum (buffer-string))))
+      (goto-char (+ (point-min) tar-chk-offset))
+      (delete-char 8)
+      (insert (format "%6o\0 " ck))
+      (when update-checksum
+       (setf (tar-header-checksum header) ck))
+      (tar-header-block-check-checksum (buffer-string)
+                                      (tar-header-checksum header)
+                                      (tar-header-name header)))
+    ;; .
+    (buffer-string)))
+
 
 (defun tar-header-block-checksum (string)
   "Compute and return a tar-acceptable checksum for this block."
-  (assert (not (multibyte-string-p string)))
+  (cl-assert (not (multibyte-string-p string)))
   (let* ((chk-field-start tar-chk-offset)
         (chk-field-end (+ chk-field-start 8))
         (sum 0)
@@ -399,7 +466,7 @@ write-date, checksum, link-type, and link-name."
 
 (defun tar-clip-time-string (time)
   (let ((str (current-time-string time)))
-    (concat " " (substring str 4 16) (substring str 19 24))))
+    (concat " " (substring str 4 16) (format-time-string " %Y" time))))
 
 (defun tar-grind-file-mode (mode)
   "Construct a `-rw--r--r--' string indicating MODE.
@@ -407,13 +474,19 @@ MODE should be an integer which is a file mode value."
   (string
    (if (zerop (logand 256 mode)) ?- ?r)
    (if (zerop (logand 128 mode)) ?- ?w)
-   (if (zerop (logand 1024 mode)) (if (zerop (logand  64 mode)) ?- ?x) ?s)
+   (if (zerop (logand 2048 mode))
+       (if (zerop (logand  64 mode)) ?- ?x)
+     (if (zerop (logand  64 mode)) ?S ?s))
    (if (zerop (logand  32 mode)) ?- ?r)
    (if (zerop (logand  16 mode)) ?- ?w)
-   (if (zerop (logand 2048 mode)) (if (zerop (logand   8 mode)) ?- ?x) ?s)
+   (if (zerop (logand 1024 mode))
+       (if (zerop (logand   8 mode)) ?- ?x)
+     (if (zerop (logand   8 mode)) ?S ?s))
    (if (zerop (logand   4 mode)) ?- ?r)
    (if (zerop (logand   2 mode)) ?- ?w)
-   (if (zerop (logand   1 mode)) ?- ?x)))
+   (if (zerop (logand 512 mode))
+       (if (zerop (logand   1 mode)) ?- ?x)
+     (if (zerop (logand   1 mode)) ?T ?t))))
 
 (defun tar-header-block-summarize (tar-hblock &optional mod-p)
   "Return a line similar to the output of `tar -vtf'."
@@ -442,7 +515,8 @@ MODE should be an integer which is a file mode value."
                  ((eq type 29) ?M)     ; multivolume continuation
                  ((eq type 35) ?S)     ; sparse
                  ((eq type 38) ?V)     ; volume header
-                 ((eq type 55) ?H)     ; extended pax header
+                 ((eq type 55) ?H)     ; pax global extended header
+                 ((eq type 72) ?X)     ; pax extended header
                  (t ?\s)
                  )
            (tar-grind-file-mode mode)
@@ -483,7 +557,7 @@ MODE should be an integer which is a file mode value."
 
 (defun tar-summarize-buffer ()
   "Parse the contents of the tar file in the current buffer."
-  (assert (tar-data-swapped-p))
+  (cl-assert (tar-data-swapped-p))
   (let* ((modified (buffer-modified-p))
          (result '())
          (pos (point-min))
@@ -505,7 +579,7 @@ MODE should be an integer which is a file mode value."
         ;;(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)))
@@ -517,12 +591,13 @@ MODE should be an integer which is a file mode value."
         (progress-reporter-done progress-reporter)
       (message "Warning: premature EOF parsing tar file"))
     (goto-char (point-min))
-    (let ((inhibit-read-only t)
+    (let ((buffer-file-truename nil) ; avoid changing dir mtime by lock_file
+         (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)))
+      (insert total-summaries "\n")
+      (goto-char (point-min))
+      (restore-buffer-modified-p modified))))
 \f
 (defvar tar-mode-map
   (let ((map (make-keymap)))
@@ -536,18 +611,18 @@ MODE should be an integer which is a file mode value."
     (define-key map "\C-m" 'tar-extract)
     (define-key map [mouse-2] 'tar-mouse-extract)
     (define-key map "g" 'revert-buffer)
-    (define-key map "h" 'describe-mode)
     (define-key map "n" 'tar-next-line)
     (define-key map "\^N" 'tar-next-line)
     (define-key map [down] 'tar-next-line)
     (define-key map "o" 'tar-extract-other-window)
     (define-key map "p" 'tar-previous-line)
-    (define-key map "q" 'quit-window)
     (define-key map "\^P" 'tar-previous-line)
     (define-key map [up] 'tar-previous-line)
+    (define-key map "I" 'tar-new-entry)
     (define-key map "R" 'tar-rename-entry)
     (define-key map "u" 'tar-unflag)
     (define-key map "v" 'tar-view)
+    (define-key map "w" 'woman-tar-extract-file)
     (define-key map "x" 'tar-expunge)
     (define-key map "\177" 'tar-unflag-backwards)
     (define-key map "E" 'tar-extract-other-window)
@@ -565,6 +640,8 @@ MODE should be an integer which is a file mode value."
     (define-key map [menu-bar immediate]
       (cons "Immediate" (make-sparse-keymap "Immediate")))
 
+    (define-key map [menu-bar immediate woman]
+      '("Read Man Page (WoMan)" . woman-tar-extract-file))
     (define-key map [menu-bar immediate view]
       '("View This File" . tar-view))
     (define-key map [menu-bar immediate display]
@@ -618,7 +695,7 @@ MODE should be an integer which is a file mode value."
   (if (buffer-live-p tar-data-buffer) (kill-buffer tar-data-buffer)))
 
 ;;;###autoload
-(define-derived-mode tar-mode nil "Tar"
+(define-derived-mode tar-mode special-mode "Tar"
   "Major mode for viewing a tar file as a dired-like listing of its contents.
 You can move around using the usual cursor motion commands.
 Letters no longer insert themselves.
@@ -633,6 +710,9 @@ 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}"
+  (and buffer-file-name
+       (file-writable-p buffer-file-name)
+       (setq buffer-read-only nil))    ; undo what `special-mode' did
   (make-local-variable 'tar-parse-info)
   (set (make-local-variable 'require-final-newline) nil) ; binary data, dude...
   (set (make-local-variable 'local-enable-local-variables) nil)
@@ -647,7 +727,7 @@ See also: variables `tar-update-datestamp' and `tar-anal-blocksize'.
   (widen)
   ;; 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)))
+  (cl-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
@@ -673,30 +753,27 @@ See also: variables `tar-update-datestamp' and `tar-anal-blocksize'.
      (fundamental-mode)
      (signal (car err) (cdr err)))))
 
+(autoload 'woman-tar-extract-file "woman"
+  "In tar mode, run the WoMan man-page browser on this file." t)
 
-(defun tar-subfile-mode (p)
+(define-minor-mode tar-subfile-mode
   "Minor mode for editing an element of a tar-file.
-This mode arranges for \"saving\" this buffer to write the data
-into the tar-file buffer that it came from.  The changes will actually
-appear on disk when you save the tar-file's buffer."
-  (interactive "P")
+With a prefix argument ARG, enable the mode if ARG is positive,
+and disable it otherwise.  If called from Lisp, enable the mode
+if ARG is omitted or nil.  This mode arranges for \"saving\" this
+buffer to write the data into the tar-file buffer that it came
+from.  The changes will actually appear on disk when you save the
+tar-file's buffer."
+  ;; Don't do this, because it is redundant and wastes mode line space.
+  ;; :lighter " TarFile"
+  nil nil nil
   (or (and (boundp 'tar-superior-buffer) tar-superior-buffer)
       (error "This buffer is not an element of a tar file"))
-  ;; Don't do this, because it is redundant and wastes mode line space.
-  ;;  (or (assq 'tar-subfile-mode minor-mode-alist)
-  ;;      (setq minor-mode-alist (append minor-mode-alist
-  ;;                                (list '(tar-subfile-mode " TarFile")))))
-  (make-local-variable 'tar-subfile-mode)
-  (setq tar-subfile-mode
-       (if (null p)
-           (not tar-subfile-mode)
-           (> (prefix-numeric-value p) 0)))
   (cond (tar-subfile-mode
         (add-hook 'write-file-functions 'tar-subfile-save-buffer nil t)
         ;; turn off auto-save.
         (auto-save-mode -1)
-        (setq buffer-auto-save-file-name nil)
-        (run-hooks 'tar-subfile-mode-hook))
+        (setq buffer-auto-save-file-name nil))
        (t
         (remove-hook 'write-file-functions 'tar-subfile-save-buffer t))))
 
@@ -726,19 +803,21 @@ appear on disk when you save the tar-file's buffer."
   (interactive "p")
   (tar-next-line (- arg)))
 
+(defun tar-current-position ()
+  "Return the `tar-parse-info' index for the current line."
+  (count-lines (point-min) (line-beginning-position)))
+
 (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) (line-beginning-position))
+  (or (nth (tar-current-position)
           tar-parse-info)
       (if noerror
          nil
          (error "This line does not describe a tar-file entry"))))
 
-(defun tar-get-descriptor ()
-  (let* ((descriptor (tar-current-descriptor))
-        (size (tar-header-size descriptor))
-        (link-p (tar-header-link-type descriptor)))
+(defun tar--check-descriptor (descriptor)
+  (let ((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")
@@ -747,11 +826,26 @@ appear on disk when you save the tar-file's buffer."
                     ((eq link-p 29) "a multivolume-continuation")
                     ((eq link-p 35) "a sparse entry")
                     ((eq link-p 38) "a volume header")
-                    ((eq link-p 55) "an extended pax header")
-                    (t "a link"))))
+                    ((eq link-p 55) "a pax global extended header")
+                    ((eq link-p 72) "a pax extended header")
+                    (t "a link"))))))
+
+(defun tar-get-descriptor ()
+  (let* ((descriptor (tar-current-descriptor))
+        (size (tar-header-size descriptor)))
+    (tar--check-descriptor descriptor)
     (if (zerop size) (message "This is a zero-length file"))
     descriptor))
 
+(defun tar-get-file-descriptor (file)
+  ;; Used by package.el.
+  (let ((desc ()))
+    (dolist (hdr tar-parse-info)
+      (when (equal file (tar-header-name hdr))
+        (setq desc hdr)))
+    (tar--check-descriptor desc)
+    desc))
+
 (defun tar-mouse-extract (event)
   "Extract a file whose tar directory line you click on."
   (interactive "e")
@@ -770,98 +864,100 @@ appear on disk when you save the tar-file's buffer."
       (let ((file-name-handler-alist nil))
        (apply op args))))
 
+(defun tar--extract (descriptor)
+  "Extract this entry of the tar file into its own buffer."
+  (let* ((name (tar-header-name descriptor))
+        (size (tar-header-size descriptor))
+        (start (tar-header-data-start descriptor))
+        (end (+ start size))
+         (tarname (buffer-name))
+         (bufname (concat (file-name-nondirectory name)
+                          " ("
+                          tarname
+                          ")"))
+         (buffer (generate-new-buffer bufname)))
+    (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-value 'enable-multibyte-characters)
+                 (coding-system-get coding :for-unibyte))
+            (with-current-buffer buffer
+              (set-buffer-multibyte nil)))
+        (widen)
+        (with-current-buffer buffer
+          (setq buffer-undo-list t))
+        (decode-coding-region start end coding buffer)
+        (with-current-buffer buffer
+          (setq buffer-undo-list nil))))
+    buffer))
+
 (defun tar-extract (&optional other-window-p)
   "In Tar mode, extract this entry of the tar file into its own buffer."
   (interactive)
   (let* ((view-p (eq other-window-p 'view))
         (descriptor (tar-get-descriptor))
         (name (tar-header-name descriptor))
-        (size (tar-header-size descriptor))
-        (start (tar-header-data-start descriptor))
-        (end (+ start size)))
-    (let* ((tar-buffer (current-buffer))
-          (tarname (buffer-name))
-          (bufname (concat (file-name-nondirectory name)
-                           " ("
-                            tarname
-                            ")"))
-          (read-only-p (or buffer-read-only view-p))
-          (new-buffer-file-name (expand-file-name
-                                 ;; `:' is not allowed on Windows
-                                  (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)
-          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)
-       (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-value '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-if-not-modified))
-       (if (eq other-window-p 'display)
-           (display-buffer buffer)
-         (if other-window-p
-             (switch-to-buffer-other-window buffer)
-           (switch-to-buffer buffer)))))))
+         (tar-buffer (current-buffer))
+         (tarname (buffer-name))
+         (read-only-p (or buffer-read-only view-p))
+         (new-buffer-file-name (expand-file-name
+                                ;; `:' is not allowed on Windows
+                                (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))
+    (unless buffer
+      (setq buffer (tar--extract descriptor))
+      (setq just-created t)
+      (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))
+        (set-buffer-modified-p nil)
+        (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)))
+    (cond
+     (view-p
+      (view-buffer buffer (and just-created 'kill-buffer-if-not-modified)))
+     ((eq other-window-p 'display) (display-buffer buffer))
+     (other-window-p (switch-to-buffer-other-window buffer))
+     (t (switch-to-buffer buffer)))))
 
 
 (defun tar-extract-other-window ()
@@ -928,6 +1024,37 @@ the current tar-entry."
        (write-region start end to-file nil nil nil t)))
     (message "Copied tar entry %s to %s" name to-file)))
 
+(defun tar-new-entry (filename &optional index)
+  "Insert a new empty regular file before point."
+  (interactive "*sFile name: ")
+  (let* ((buffer  (current-buffer))
+        (index   (or index (tar-current-position)))
+        (d-list  (and (not (zerop index))
+                      (nthcdr (+ -1 index) tar-parse-info)))
+        (pos     (if d-list
+                     (tar-header-data-end (car d-list))
+                   (point-min)))
+        (new-descriptor
+         (tar-new-regular-file-header filename)))
+    ;; Update the data buffer; fill the missing descriptor fields.
+    (with-current-buffer tar-data-buffer
+      (goto-char pos)
+      (insert (tar-header-serialize new-descriptor t))
+      (setf  (tar-header-data-start new-descriptor)
+            (copy-marker (point) nil)))
+    ;; Update tar-parse-info.
+    (if d-list
+       (setcdr d-list     (cons new-descriptor (cdr d-list)))
+      (setq tar-parse-info (cons new-descriptor tar-parse-info)))
+    ;; Update the listing buffer.
+    (save-excursion
+      (goto-char (point-min))
+      (forward-line index)
+      (let ((inhibit-read-only t))
+       (insert (tar-header-block-summarize new-descriptor) ?\n)))
+    ;; .
+    index))
+
 (defun tar-flag-deleted (p &optional unflag)
   "In Tar mode, mark this sub-file to be deleted from the tar file.
 With a prefix argument, mark that many files."
@@ -1117,15 +1244,15 @@ for this to be permanent."
                      (insert (tar-header-block-summarize descriptor) "\n")))
     (forward-line -1) (move-to-column col))
 
-  (assert (tar-data-swapped-p))
+  (cl-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))))
+        (cl-assert (not (or enable-multibyte-characters
+                            (multibyte-string-p new-data-string))))
         (insert new-data-string)
         ;;
         ;; compute a new checksum and insert it.
@@ -1168,7 +1295,6 @@ to make your changes permanent."
         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)
@@ -1248,7 +1374,7 @@ Leaves the region wide."
 
 
 ;; Used in write-region-annotate-functions to write tar-files out correctly.
-(defun tar-write-region-annotate (start end)
+(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.
@@ -1259,5 +1385,4 @@ Leaves the region wide."
 
 (provide 'tar-mode)
 
-;; arch-tag: 8a585a4a-340e-42c2-89e7-d3b1013a4b78
 ;;; tar-mode.el ends here