EasyPG: Rename epa-mail-minor-mode to epa-global-mail-mode.
[bpt/emacs.git] / lisp / tar-mode.el
CommitLineData
c88ab9ce 1;;; tar-mode.el --- simple editing of tar files from GNU emacs
aa73f29c 2
0d30b337 3;; Copyright (C) 1990, 1991, 1993, 1994, 1995, 1996, 1997, 1998, 1999,
409cc4a3 4;; 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008 Free Software Foundation, Inc.
eea8d4ef 5
22a89ee8 6;; Author: Jamie Zawinski <jwz@lucid.com>
54138c9d 7;; Maintainer: FSF
e5167999 8;; Created: 04 Apr 1990
d7b4d18f 9;; Keywords: unix
aa73f29c 10
b578f267
EN
11;; This file is part of GNU Emacs.
12
13;; GNU Emacs is free software; you can redistribute it and/or modify
14;; it under the terms of the GNU General Public License as published by
b4aa6026 15;; the Free Software Foundation; either version 3, or (at your option)
b578f267
EN
16;; any later version.
17
18;; GNU Emacs is distributed in the hope that it will be useful,
19;; but WITHOUT ANY WARRANTY; without even the implied warranty of
20;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21;; GNU General Public License for more details.
22
23;; You should have received a copy of the GNU General Public License
24;; along with GNU Emacs; see the file COPYING. If not, write to the
086add15
LK
25;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
26;; Boston, MA 02110-1301, USA.
aa73f29c 27
22a89ee8
ER
28;;; Commentary:
29
b578f267
EN
30;; This package attempts to make dealing with Unix 'tar' archives easier.
31;; When this code is loaded, visiting a file whose name ends in '.tar' will
32;; cause the contents of that archive file to be displayed in a Dired-like
33;; listing. It is then possible to use the customary Dired keybindings to
34;; extract sub-files from that archive, either by reading them into their own
35;; editor buffers, or by copying them directly to arbitrary files on disk.
36;; It is also possible to delete sub-files from within the tar file and write
37;; the modified archive back to disk, or to edit sub-files within the archive
38;; and re-insert the modified files into the archive. See the documentation
39;; string of tar-mode for more info.
40
41;; This code now understands the extra fields that GNU tar adds to tar files.
42
43;; This interacts correctly with "uncompress.el" in the Emacs library,
f1180544 44;; which you get with
b578f267
EN
45;;
46;; (autoload 'uncompress-while-visiting "uncompress")
47;; (setq auto-mode-alist (cons '("\\.Z$" . uncompress-while-visiting)
48;; auto-mode-alist))
49;;
50;; Do not attempt to use tar-mode.el with crypt.el, you will lose.
51
f1180544 52;; *************** TO DO ***************
b578f267
EN
53;;
54;; o chmod should understand "a+x,og-w".
55;;
f1180544 56;; o It's not possible to add a NEW file to a tar archive; not that
b578f267
EN
57;; important, but still...
58;;
59;; o The code is less efficient that it could be - in a lot of places, I
60;; pull a 512-character string out of the buffer and parse it, when I could
61;; be parsing it in place, not garbaging a string. Should redo that.
62;;
63;; o I'd like a command that searches for a string/regexp in every subfile
64;; of an archive, where <esc> would leave you in a subfile-edit buffer.
65;; (Like the Meta-R command of the Zmacs mail reader.)
66;;
f1180544 67;; o Sometimes (but not always) reverting the tar-file buffer does not
b578f267
EN
68;; re-grind the listing, and you are staring at the binary tar data.
69;; Typing 'g' again immediately after that will always revert and re-grind
70;; it, though. I have no idea why this happens.
71;;
72;; o Tar-mode interacts poorly with crypt.el and zcat.el because the tar
73;; write-file-hook actually writes the file. Instead it should remove the
74;; header (and conspire to put it back afterwards) so that other write-file
75;; hooks which frob the buffer have a chance to do their dirty work. There
76;; might be a problem if the tar write-file-hook does not come *first* on
77;; the list.
78;;
f1180544 79;; o Block files, sparse files, continuation files, and the various header
b578f267
EN
80;; types aren't editable. Actually I don't know that they work at all.
81
82;; Rationale:
83
84;; Why does tar-mode edit the file itself instead of using tar?
85
86;; That means that you can edit tar files which you don't have room for
87;; on your local disk.
88
89;; I don't know about recent features in gnu tar, but old versions of tar
90;; can't replace a file in the middle of a tar file with a new version.
91;; Tar-mode can. I don't think tar can do things like chmod the subfiles.
92;; An implementation which involved unpacking and repacking the file into
93;; some scratch directory would be very wasteful, and wouldn't be able to
94;; preserve the file owners.
ce210066 95
22a89ee8
ER
96;;; Code:
97
b4dd2e66
SE
98(defgroup tar nil
99 "Simple editing of tar files."
100 :prefix "tar-"
101 :group 'data)
102
103(defcustom tar-anal-blocksize 20
8560523d 104 "The blocksize of tar files written by Emacs, or nil, meaning don't care.
aa73f29c 105The blocksize of a tar file is not really the size of the blocks; rather, it is
f1180544 106the number of blocks written with one system call. When tarring to a tape,
aa73f29c
RS
107this is the size of the *tape* blocks, but when writing to a file, it doesn't
108matter much. The only noticeable difference is that if a tar file does not
109have a blocksize of 20, tar will tell you that; all this really controls is
b4dd2e66
SE
110how many null padding bytes go on the end of the tar file."
111 :type '(choice integer (const nil))
112 :group 'tar)
aa73f29c 113
b4dd2e66 114(defcustom tar-update-datestamp nil
8560523d 115 "Non-nil means Tar mode should play fast and loose with sub-file datestamps.
30712209 116If this is true, then editing and saving a tar file entry back into its
aa73f29c
RS
117tar file will update its datestamp. If false, the datestamp is unchanged.
118You may or may not want this - it is good in that you can tell when a file
119in a tar archive has been changed, but it is bad for the same reason that
f1180544 120editing a file in the tar archive at all is bad - the changed version of
b4dd2e66
SE
121the file never exists on disk."
122 :type 'boolean
123 :group 'tar)
aa73f29c 124
b4dd2e66 125(defcustom tar-mode-show-date nil
8560523d 126 "Non-nil means Tar mode should show the date/time of each subfile.
b4dd2e66
SE
127This information is useful, but it takes screen space away from file names."
128 :type 'boolean
129 :group 'tar)
30712209 130
0f8becaa
ER
131(defvar tar-parse-info nil)
132(defvar tar-header-offset nil)
133(defvar tar-superior-buffer nil)
134(defvar tar-superior-descriptor nil)
135(defvar tar-subfile-mode nil)
d26e6665 136(defvar tar-file-name-coding-system nil)
1c0b3743
RS
137
138(put 'tar-parse-info 'permanent-local t)
139(put 'tar-header-offset 'permanent-local t)
140(put 'tar-superior-buffer 'permanent-local t)
141(put 'tar-superior-descriptor 'permanent-local t)
d26e6665 142(put 'tar-file-name-coding-system 'permanent-local t)
aa73f29c 143\f
aa73f29c
RS
144(defmacro tar-setf (form val)
145 "A mind-numbingly simple implementation of setf."
146 (let ((mform (macroexpand form (and (boundp 'byte-compile-macro-environment)
147 byte-compile-macro-environment))))
148 (cond ((symbolp mform) (list 'setq mform val))
149 ((not (consp mform)) (error "can't setf %s" form))
150 ((eq (car mform) 'aref)
151 (list 'aset (nth 1 mform) (nth 2 mform) val))
152 ((eq (car mform) 'car)
153 (list 'setcar (nth 1 mform) val))
154 ((eq (car mform) 'cdr)
155 (list 'setcdr (nth 1 mform) val))
156 (t (error "don't know how to setf %s" form)))))
aa73f29c
RS
157\f
158;;; down to business.
159
160(defmacro make-tar-header (name mode uid git size date ck lt ln
161 magic uname gname devmaj devmin)
162 (list 'vector name mode uid git size date ck lt ln
163 magic uname gname devmaj devmin))
164
165(defmacro tar-header-name (x) (list 'aref x 0))
166(defmacro tar-header-mode (x) (list 'aref x 1))
167(defmacro tar-header-uid (x) (list 'aref x 2))
168(defmacro tar-header-gid (x) (list 'aref x 3))
169(defmacro tar-header-size (x) (list 'aref x 4))
170(defmacro tar-header-date (x) (list 'aref x 5))
171(defmacro tar-header-checksum (x) (list 'aref x 6))
172(defmacro tar-header-link-type (x) (list 'aref x 7))
173(defmacro tar-header-link-name (x) (list 'aref x 8))
174(defmacro tar-header-magic (x) (list 'aref x 9))
175(defmacro tar-header-uname (x) (list 'aref x 10))
176(defmacro tar-header-gname (x) (list 'aref x 11))
177(defmacro tar-header-dmaj (x) (list 'aref x 12))
178(defmacro tar-header-dmin (x) (list 'aref x 13))
179
180(defmacro make-tar-desc (data-start tokens)
181 (list 'cons data-start tokens))
182
183(defmacro tar-desc-data-start (x) (list 'car x))
184(defmacro tar-desc-tokens (x) (list 'cdr x))
185
186(defconst tar-name-offset 0)
187(defconst tar-mode-offset (+ tar-name-offset 100))
188(defconst tar-uid-offset (+ tar-mode-offset 8))
189(defconst tar-gid-offset (+ tar-uid-offset 8))
190(defconst tar-size-offset (+ tar-gid-offset 8))
191(defconst tar-time-offset (+ tar-size-offset 12))
192(defconst tar-chk-offset (+ tar-time-offset 12))
193(defconst tar-linkp-offset (+ tar-chk-offset 8))
194(defconst tar-link-offset (+ tar-linkp-offset 1))
195;;; GNU-tar specific slots.
196(defconst tar-magic-offset (+ tar-link-offset 100))
197(defconst tar-uname-offset (+ tar-magic-offset 8))
198(defconst tar-gname-offset (+ tar-uname-offset 32))
199(defconst tar-dmaj-offset (+ tar-gname-offset 32))
200(defconst tar-dmin-offset (+ tar-dmaj-offset 8))
201(defconst tar-end-offset (+ tar-dmin-offset 8))
202
1bc28099 203(defun tar-header-block-tokenize (string)
e865c5ce 204 "Return a `tar-header' structure.
f1180544 205This is a list of name, mode, uid, gid, size,
e865c5ce 206write-date, checksum, link-type, and link-name."
8f924df7 207 (setq string (string-as-unibyte string))
aa73f29c
RS
208 (cond ((< (length string) 512) nil)
209 (;(some 'plusp string) ; <-- oops, massive cycle hog!
210 (or (not (= 0 (aref string 0))) ; This will do.
211 (not (= 0 (aref string 101))))
212 (let* ((name-end (1- tar-mode-offset))
213 (link-end (1- tar-magic-offset))
214 (uname-end (1- tar-gname-offset))
215 (gname-end (1- tar-dmaj-offset))
216 (link-p (aref string tar-linkp-offset))
217 (magic-str (substring string tar-magic-offset (1- tar-uname-offset)))
218 (uname-valid-p (or (string= "ustar " magic-str) (string= "GNUtar " magic-str)))
5342b2c7 219 name linkname
aa73f29c 220 (nulsexp "[^\000]*\000"))
d710de41
RS
221 (when (string-match nulsexp string tar-name-offset)
222 (setq name-end (min name-end (1- (match-end 0)))))
223 (when (string-match nulsexp string tar-link-offset)
224 (setq link-end (min link-end (1- (match-end 0)))))
225 (when (string-match nulsexp string tar-uname-offset)
226 (setq uname-end (min uname-end (1- (match-end 0)))))
227 (when (string-match nulsexp string tar-gname-offset)
228 (setq gname-end (min gname-end (1- (match-end 0)))))
aa73f29c
RS
229 (setq name (substring string tar-name-offset name-end)
230 link-p (if (or (= link-p 0) (= link-p ?0))
231 nil
232 (- link-p ?0)))
1b33e237
KH
233 (setq linkname (substring string tar-link-offset link-end))
234 (if default-enable-multibyte-characters
235 (setq name
d26e6665 236 (decode-coding-string name tar-file-name-coding-system)
1b33e237 237 linkname
d26e6665
KH
238 (decode-coding-string linkname
239 tar-file-name-coding-system)))
c40d3365 240 (if (and (null link-p) (string-match "/\\'" name)) (setq link-p 5)) ; directory
aa73f29c
RS
241 (make-tar-header
242 name
248d750a
KH
243 (tar-parse-octal-integer string tar-mode-offset tar-uid-offset)
244 (tar-parse-octal-integer string tar-uid-offset tar-gid-offset)
245 (tar-parse-octal-integer string tar-gid-offset tar-size-offset)
246 (tar-parse-octal-integer string tar-size-offset tar-time-offset)
247 (tar-parse-octal-long-integer string tar-time-offset tar-chk-offset)
248 (tar-parse-octal-integer string tar-chk-offset tar-linkp-offset)
aa73f29c 249 link-p
1b33e237 250 linkname
aa73f29c
RS
251 uname-valid-p
252 (and uname-valid-p (substring string tar-uname-offset uname-end))
253 (and uname-valid-p (substring string tar-gname-offset gname-end))
248d750a
KH
254 (tar-parse-octal-integer string tar-dmaj-offset tar-dmin-offset)
255 (tar-parse-octal-integer string tar-dmin-offset tar-end-offset)
aa73f29c
RS
256 )))
257 (t 'empty-tar-block)))
258
259
260(defun tar-parse-octal-integer (string &optional start end)
aa73f29c
RS
261 (if (null start) (setq start 0))
262 (if (null end) (setq end (length string)))
263 (if (= (aref string start) 0)
264 0
265 (let ((n 0))
266 (while (< start end)
267 (setq n (if (< (aref string start) ?0) n
30712209 268 (+ (* n 8) (- (aref string start) ?0)))
aa73f29c
RS
269 start (1+ start)))
270 n)))
271
30712209
RS
272(defun tar-parse-octal-long-integer (string &optional start end)
273 (if (null start) (setq start 0))
274 (if (null end) (setq end (length string)))
275 (if (= (aref string start) 0)
806cba9c 276 (list 0 0)
30712209
RS
277 (let ((lo 0)
278 (hi 0))
279 (while (< start end)
280 (if (>= (aref string start) ?0)
281 (setq lo (+ (* lo 8) (- (aref string start) ?0))
282 hi (+ (* hi 8) (ash lo -16))
283 lo (logand lo 65535)))
284 (setq start (1+ start)))
285 (list hi lo))))
286
aa73f29c 287(defun tar-parse-octal-integer-safe (string)
8560523d
SM
288 (if (zerop (length string)) (error "empty string"))
289 (mapc (lambda (c)
290 (if (or (< c ?0) (> c ?7))
291 (error "`%c' is not an octal digit" c)))
292 string)
aa73f29c
RS
293 (tar-parse-octal-integer string))
294
295
1bc28099 296(defun tar-header-block-checksum (string)
e865c5ce 297 "Compute and return a tar-acceptable checksum for this block."
8f924df7 298 (setq string (string-as-unibyte string))
aa73f29c
RS
299 (let* ((chk-field-start tar-chk-offset)
300 (chk-field-end (+ chk-field-start 8))
301 (sum 0)
302 (i 0))
303 ;; Add up all of the characters except the ones in the checksum field.
304 ;; Add that field as if it were filled with spaces.
305 (while (< i chk-field-start)
306 (setq sum (+ sum (aref string i))
307 i (1+ i)))
308 (setq i chk-field-end)
309 (while (< i 512)
310 (setq sum (+ sum (aref string i))
311 i (1+ i)))
312 (+ sum (* 32 8))))
313
1bc28099 314(defun tar-header-block-check-checksum (hblock desired-checksum file-name)
aa73f29c 315 "Beep and print a warning if the checksum doesn't match."
1bc28099 316 (if (not (= desired-checksum (tar-header-block-checksum hblock)))
aa73f29c
RS
317 (progn (beep) (message "Invalid checksum for file %s!" file-name))))
318
30712209
RS
319(defun tar-clip-time-string (time)
320 (let ((str (current-time-string time)))
139f2b7c 321 (concat " " (substring str 4 16) (substring str 19 24))))
aa73f29c 322
139f2b7c
SM
323(defun tar-grind-file-mode (mode)
324 "Construct a `-rw--r--r--' string indicating MODE.
6a06ace6 325MODE should be an integer which is a file mode value."
139f2b7c
SM
326 (string
327 (if (zerop (logand 256 mode)) ?- ?r)
328 (if (zerop (logand 128 mode)) ?- ?w)
329 (if (zerop (logand 1024 mode)) (if (zerop (logand 64 mode)) ?- ?x) ?s)
330 (if (zerop (logand 32 mode)) ?- ?r)
331 (if (zerop (logand 16 mode)) ?- ?w)
332 (if (zerop (logand 2048 mode)) (if (zerop (logand 8 mode)) ?- ?x) ?s)
333 (if (zerop (logand 4 mode)) ?- ?r)
334 (if (zerop (logand 2 mode)) ?- ?w)
335 (if (zerop (logand 1 mode)) ?- ?x)))
aa73f29c 336
1bc28099 337(defun tar-header-block-summarize (tar-hblock &optional mod-p)
139f2b7c 338 "Return a line similar to the output of `tar -vtf'."
aa73f29c
RS
339 (let ((name (tar-header-name tar-hblock))
340 (mode (tar-header-mode tar-hblock))
341 (uid (tar-header-uid tar-hblock))
342 (gid (tar-header-gid tar-hblock))
343 (uname (tar-header-uname tar-hblock))
344 (gname (tar-header-gname tar-hblock))
345 (size (tar-header-size tar-hblock))
346 (time (tar-header-date tar-hblock))
8560523d 347 ;; (ck (tar-header-checksum tar-hblock))
139f2b7c
SM
348 (type (tar-header-link-type tar-hblock))
349 (link-name (tar-header-link-name tar-hblock)))
350 (format "%c%c%s%8s/%-8s%7s%s %s%s"
351 (if mod-p ?* ? )
aa73f29c 352 (cond ((or (eq type nil) (eq type 0)) ?-)
6ee30a2a
RS
353 ((eq type 1) ?h) ; link
354 ((eq type 2) ?l) ; symlink
139f2b7c
SM
355 ((eq type 3) ?c) ; char special
356 ((eq type 4) ?b) ; block special
357 ((eq type 5) ?d) ; directory
358 ((eq type 6) ?p) ; FIFO/pipe
359 ((eq type 20) ?*) ; directory listing
f9580781 360 ((eq type 28) ?L) ; next has longname
139f2b7c
SM
361 ((eq type 29) ?M) ; multivolume continuation
362 ((eq type 35) ?S) ; sparse
363 ((eq type 38) ?V) ; volume header
133ecc54 364 ((eq type 55) ?H) ; extended pax header
e5d56b61 365 (t ?\s)
139f2b7c
SM
366 )
367 (tar-grind-file-mode mode)
368 (if (= 0 (length uname)) uid uname)
369 (if (= 0 (length gname)) gid gname)
370 size
371 (if tar-mode-show-date (tar-clip-time-string time) "")
a28eaf2c
EZ
372 (propertize name
373 'mouse-face 'highlight
374 'help-echo "mouse-2: extract this file into a buffer")
139f2b7c
SM
375 (if (or (eq type 1) (eq type 2))
376 (concat (if (= type 1) " ==> " " --> ") link-name)
377 ""))))
aa73f29c 378
27129192 379(defun tar-untar-buffer ()
643415c4 380 "Extract all archive members in the tar-file into the current directory."
27129192
RS
381 (interactive)
382 (let ((multibyte enable-multibyte-characters))
383 (unwind-protect
384 (save-restriction
385 (widen)
386 (set-buffer-multibyte nil)
387 (dolist (descriptor tar-parse-info)
388 (let* ((tokens (tar-desc-tokens descriptor))
389 (name (tar-header-name tokens))
390 (dir (file-name-directory name))
643415c4
SM
391 (start (+ (tar-desc-data-start descriptor)
392 (- tar-header-offset (point-min))))
27129192 393 (end (+ start (tar-header-size tokens))))
76bf6666
RS
394 (unless (file-directory-p name)
395 (message "Extracting %s" name)
396 (if (and dir (not (file-exists-p dir)))
397 (make-directory dir t))
643415c4
SM
398 (unless (file-directory-p name)
399 (write-region start end name))
76bf6666 400 (set-file-modes name (tar-header-mode tokens))))))
1236f7f6
KH
401 (if multibyte
402 (set-buffer-multibyte 'to)))))
27129192 403
aa73f29c 404(defun tar-summarize-buffer ()
e865c5ce
RS
405 "Parse the contents of the tar file in the current buffer.
406Place a dired-like listing on the front;
407then narrow to it, so that only that listing
aa73f29c 408is visible (and the real data of the buffer is hidden)."
c40d3365
MB
409 (let ((modified (buffer-modified-p)))
410 (set-buffer-multibyte nil)
411 (let* ((result '())
412 (pos (point-min))
413 (progress-reporter
414 (make-progress-reporter "Parsing tar file..."
415 (point-min) (max 1 (- (buffer-size) 1024))))
416 tokens)
417 (while (and (<= (+ pos 512) (point-max))
418 (not (eq 'empty-tar-block
419 (setq tokens
420 (tar-header-block-tokenize
421 (buffer-substring pos (+ pos 512)))))))
422 (setq pos (+ pos 512))
423 (progress-reporter-update progress-reporter pos)
133ecc54 424 (if (memq (tar-header-link-type tokens) '(20 55))
c40d3365
MB
425 ;; Foo. There's an extra empty block after these.
426 (setq pos (+ pos 512)))
427 (let ((size (tar-header-size tokens)))
428 (if (< size 0)
429 (error "%s has size %s - corrupted"
430 (tar-header-name tokens) size))
431 ;;
432 ;; This is just too slow. Don't really need it anyway....
433 ;;(tar-header-block-check-checksum
434 ;; hblock (tar-header-block-checksum hblock)
435 ;; (tar-header-name tokens))
436
437 (push (make-tar-desc pos tokens) result)
438
439 (and (null (tar-header-link-type tokens))
440 (> size 0)
441 (setq pos
442 (+ pos 512 (ash (ash (1- size) -9) 9)) ; this works
443 ;;(+ pos (+ size (- 512 (rem (1- size) 512)))) ; this doesn't
444 ))))
445 (make-local-variable 'tar-parse-info)
446 (setq tar-parse-info (nreverse result))
447 ;; A tar file should end with a block or two of nulls,
448 ;; but let's not get a fatal error if it doesn't.
449 (if (eq tokens 'empty-tar-block)
450 (progress-reporter-done progress-reporter)
451 (message "Warning: premature EOF parsing tar file")))
452 ;; Obey the user's preference for the use of uni/multibytes.
1236f7f6
KH
453 (if default-enable-multibyte-characters
454 (set-buffer-multibyte 'to))
aa73f29c 455 (goto-char (point-min))
c40d3365
MB
456 (let ((inhibit-read-only t)
457 ;; Collect summary lines and insert them all at once since tar files
458 ;; can be pretty big.
459 (total-summaries
460 (mapconcat
461 (lambda (tar-desc)
462 (tar-header-block-summarize (tar-desc-tokens tar-desc)))
463 tar-parse-info
464 "\n")))
465 (insert total-summaries "\n"))
466 (narrow-to-region (point-min) (point))
467 (set (make-local-variable 'tar-header-offset) (position-bytes (point)))
468 (goto-char (point-min))
469 (restore-buffer-modified-p modified)))
e865c5ce 470\f
c40d3365
MB
471(defvar tar-mode-map
472 (let ((map (make-keymap)))
473 (suppress-keymap map)
474 (define-key map " " 'tar-next-line)
475 (define-key map "C" 'tar-copy)
476 (define-key map "d" 'tar-flag-deleted)
477 (define-key map "\^D" 'tar-flag-deleted)
478 (define-key map "e" 'tar-extract)
479 (define-key map "f" 'tar-extract)
480 (define-key map "\C-m" 'tar-extract)
481 (define-key map [mouse-2] 'tar-mouse-extract)
482 (define-key map "g" 'revert-buffer)
483 (define-key map "h" 'describe-mode)
484 (define-key map "n" 'tar-next-line)
485 (define-key map "\^N" 'tar-next-line)
486 (define-key map [down] 'tar-next-line)
487 (define-key map "o" 'tar-extract-other-window)
488 (define-key map "p" 'tar-previous-line)
489 (define-key map "q" 'quit-window)
490 (define-key map "\^P" 'tar-previous-line)
491 (define-key map [up] 'tar-previous-line)
492 (define-key map "R" 'tar-rename-entry)
493 (define-key map "u" 'tar-unflag)
494 (define-key map "v" 'tar-view)
495 (define-key map "x" 'tar-expunge)
496 (define-key map "\177" 'tar-unflag-backwards)
497 (define-key map "E" 'tar-extract-other-window)
498 (define-key map "M" 'tar-chmod-entry)
499 (define-key map "G" 'tar-chgrp-entry)
500 (define-key map "O" 'tar-chown-entry)
e865c5ce 501\f
c40d3365 502 ;; Make menu bar items.
e865c5ce 503
c40d3365
MB
504 ;; Get rid of the Edit menu bar item to save space.
505 (define-key map [menu-bar edit] 'undefined)
e865c5ce 506
c40d3365 507 (define-key map [menu-bar immediate]
e865c5ce
RS
508 (cons "Immediate" (make-sparse-keymap "Immediate")))
509
c40d3365 510 (define-key map [menu-bar immediate view]
e865c5ce 511 '("View This File" . tar-view))
c40d3365 512 (define-key map [menu-bar immediate display]
ddcb4671 513 '("Display in Other Window" . tar-display-other-window))
c40d3365 514 (define-key map [menu-bar immediate find-file-other-window]
e865c5ce 515 '("Find in Other Window" . tar-extract-other-window))
c40d3365 516 (define-key map [menu-bar immediate find-file]
e865c5ce
RS
517 '("Find This File" . tar-extract))
518
c40d3365 519 (define-key map [menu-bar mark]
e865c5ce
RS
520 (cons "Mark" (make-sparse-keymap "Mark")))
521
c40d3365 522 (define-key map [menu-bar mark unmark-all]
e865c5ce 523 '("Unmark All" . tar-clear-modification-flags))
c40d3365 524 (define-key map [menu-bar mark deletion]
e865c5ce 525 '("Flag" . tar-flag-deleted))
c40d3365 526 (define-key map [menu-bar mark unmark]
e865c5ce
RS
527 '("Unflag" . tar-unflag))
528
c40d3365 529 (define-key map [menu-bar operate]
e865c5ce
RS
530 (cons "Operate" (make-sparse-keymap "Operate")))
531
c40d3365 532 (define-key map [menu-bar operate chown]
e865c5ce 533 '("Change Owner..." . tar-chown-entry))
c40d3365 534 (define-key map [menu-bar operate chgrp]
e865c5ce 535 '("Change Group..." . tar-chgrp-entry))
c40d3365 536 (define-key map [menu-bar operate chmod]
e865c5ce 537 '("Change Mode..." . tar-chmod-entry))
c40d3365 538 (define-key map [menu-bar operate rename]
e865c5ce 539 '("Rename to..." . tar-rename-entry))
c40d3365 540 (define-key map [menu-bar operate copy]
e865c5ce 541 '("Copy to..." . tar-copy))
c40d3365 542 (define-key map [menu-bar operate expunge]
9f1250c5 543 '("Expunge Marked Files" . tar-expunge))
e865c5ce 544\f
c40d3365
MB
545 map)
546 "Local keymap for Tar mode listings.")
547
548\f
aa73f29c
RS
549;; tar mode is suitable only for specially formatted data.
550(put 'tar-mode 'mode-class 'special)
551(put 'tar-subfile-mode 'mode-class 'special)
552
bdd53bbc 553;;;###autoload
a91c76d6 554(define-derived-mode tar-mode nil "Tar"
aa73f29c 555 "Major mode for viewing a tar file as a dired-like listing of its contents.
f1180544 556You can move around using the usual cursor motion commands.
aa73f29c 557Letters no longer insert themselves.
439fa06f
RS
558Type `e' to pull a file out of the tar file and into its own buffer;
559or click mouse-2 on the file's line in the Tar mode buffer.
e865c5ce 560Type `c' to copy an entry from the tar file into another file on disk.
aa73f29c 561
f1180544 562If you edit a sub-file of this archive (as with the `e' command) and
8560523d 563save it with \\[save-buffer], the contents of that buffer will be
f1180544 564saved back into the tar-file buffer; in this way you can edit a file
aa73f29c
RS
565inside of a tar archive without extracting it and re-archiving it.
566
e865c5ce 567See also: variables `tar-update-datestamp' and `tar-anal-blocksize'.
aa73f29c
RS
568\\{tar-mode-map}"
569 ;; this is not interactive because you shouldn't be turning this
570 ;; mode on and off. You can corrupt things that way.
1c0b3743
RS
571 ;; rms: with permanent locals, it should now be possible to make this work
572 ;; interactively in some reasonable fashion.
aa73f29c
RS
573 (make-local-variable 'tar-header-offset)
574 (make-local-variable 'tar-parse-info)
a91c76d6
SM
575 (set (make-local-variable 'require-final-newline) nil) ; binary data, dude...
576 (set (make-local-variable 'revert-buffer-function) 'tar-mode-revert)
577 (set (make-local-variable 'local-enable-local-variables) nil)
578 (set (make-local-variable 'next-line-add-newlines) nil)
d26e6665
KH
579 (set (make-local-variable 'tar-file-name-coding-system)
580 (or file-name-coding-system
581 default-file-name-coding-system
582 locale-coding-system))
b9ca3cdb 583 ;; Prevent loss of data when saving the file.
a91c76d6 584 (set (make-local-variable 'file-precious-flag) t)
aa73f29c 585 (auto-save-mode 0)
bea824fd 586 (set (make-local-variable 'write-contents-functions) '(tar-mode-write-file))
03b00795 587 (buffer-disable-undo)
aa73f29c
RS
588 (widen)
589 (if (and (boundp 'tar-header-offset) tar-header-offset)
8f924df7 590 (narrow-to-region (point-min) tar-header-offset)
a91c76d6
SM
591 (tar-summarize-buffer)
592 (tar-next-line 0)))
aa73f29c
RS
593
594
595(defun tar-subfile-mode (p)
596 "Minor mode for editing an element of a tar-file.
76605277
RS
597This mode arranges for \"saving\" this buffer to write the data
598into the tar-file buffer that it came from. The changes will actually
599appear on disk when you save the tar-file's buffer."
aa73f29c 600 (interactive "P")
0f8becaa 601 (or (and (boundp 'tar-superior-buffer) tar-superior-buffer)
e865c5ce 602 (error "This buffer is not an element of a tar file"))
643415c4
SM
603 ;; Don't do this, because it is redundant and wastes mode line space.
604 ;; (or (assq 'tar-subfile-mode minor-mode-alist)
605 ;; (setq minor-mode-alist (append minor-mode-alist
606 ;; (list '(tar-subfile-mode " TarFile")))))
aa73f29c
RS
607 (make-local-variable 'tar-subfile-mode)
608 (setq tar-subfile-mode
609 (if (null p)
610 (not tar-subfile-mode)
611 (> (prefix-numeric-value p) 0)))
612 (cond (tar-subfile-mode
76bf6666 613 (add-hook 'write-file-functions 'tar-subfile-save-buffer nil t)
aa73f29c 614 ;; turn off auto-save.
75238436 615 (auto-save-mode -1)
aa73f29c
RS
616 (setq buffer-auto-save-file-name nil)
617 (run-hooks 'tar-subfile-mode-hook))
e865c5ce 618 (t
76bf6666 619 (remove-hook 'write-file-functions 'tar-subfile-save-buffer t))))
aa73f29c
RS
620
621
e865c5ce 622;; Revert the buffer and recompute the dired-like listing.
f6b0805a 623(defun tar-mode-revert (&optional no-auto-save no-confirm)
3742f39f
RS
624 (let ((revert-buffer-function nil)
625 (old-offset tar-header-offset)
626 success)
627 (setq tar-header-offset nil)
628 (unwind-protect
629 (and (revert-buffer t no-confirm)
630 (progn (widen)
631 (setq success t)
632 (tar-mode)))
633 ;; If the revert was canceled,
634 ;; put back the old value of tar-header-offset.
635 (or success
636 (setq tar-header-offset old-offset)))))
aa73f29c
RS
637
638
210f943e
PJ
639(defun tar-next-line (arg)
640 "Move cursor vertically down ARG lines and to the start of the filename."
aa73f29c 641 (interactive "p")
210f943e 642 (forward-line arg)
30712209 643 (if (eobp) nil (forward-char (if tar-mode-show-date 54 36))))
aa73f29c 644
210f943e
PJ
645(defun tar-previous-line (arg)
646 "Move cursor vertically up ARG lines and to the start of the filename."
aa73f29c 647 (interactive "p")
210f943e 648 (tar-next-line (- arg)))
aa73f29c
RS
649
650(defun tar-current-descriptor (&optional noerror)
e865c5ce 651 "Return the tar-descriptor of the current line, or signals an error."
aa73f29c
RS
652 ;; I wish lines had plists, like in ZMACS...
653 (or (nth (count-lines (point-min)
654 (save-excursion (beginning-of-line) (point)))
655 tar-parse-info)
656 (if noerror
657 nil
439fa06f 658 (error "This line does not describe a tar-file entry"))))
aa73f29c 659
439fa06f
RS
660(defun tar-get-descriptor ()
661 (let* ((descriptor (tar-current-descriptor))
662 (tokens (tar-desc-tokens descriptor))
663 (size (tar-header-size tokens))
664 (link-p (tar-header-link-type tokens)))
665 (if link-p
bf65c664
CY
666 (error "This is %s, not a real file"
667 (cond ((eq link-p 5) "a directory")
668 ((eq link-p 20) "a tar directory header")
669 ((eq link-p 28) "a next has longname")
670 ((eq link-p 29) "a multivolume-continuation")
671 ((eq link-p 35) "a sparse entry")
672 ((eq link-p 38) "a volume header")
673 ((eq link-p 55) "an extended pax header")
674 (t "a link"))))
f09bbd93 675 (if (zerop size) (message "This is a zero-length file"))
439fa06f
RS
676 descriptor))
677
678(defun tar-mouse-extract (event)
679 "Extract a file whose tar directory line you click on."
680 (interactive "e")
681 (save-excursion
682 (set-buffer (window-buffer (posn-window (event-end event))))
683 (save-excursion
684 (goto-char (posn-point (event-end event)))
685 ;; Just make sure this doesn't get an error.
686 (tar-get-descriptor)))
687 (select-window (posn-window (event-end event)))
688 (goto-char (posn-point (event-end event)))
689 (tar-extract))
aa73f29c 690
07e7a05a
EZ
691(defun tar-file-name-handler (op &rest args)
692 "Helper function for `tar-extract'."
693 (or (eq op 'file-exists-p)
694 (let ((file-name-handler-alist nil))
695 (apply op args))))
696
aa73f29c 697(defun tar-extract (&optional other-window-p)
628d6cef 698 "In Tar mode, extract this entry of the tar file into its own buffer."
aa73f29c
RS
699 (interactive)
700 (let* ((view-p (eq other-window-p 'view))
439fa06f 701 (descriptor (tar-get-descriptor))
aa73f29c
RS
702 (tokens (tar-desc-tokens descriptor))
703 (name (tar-header-name tokens))
704 (size (tar-header-size tokens))
643415c4
SM
705 (start (+ (tar-desc-data-start descriptor)
706 (- tar-header-offset (point-min))))
aa73f29c 707 (end (+ start size)))
aa73f29c 708 (let* ((tar-buffer (current-buffer))
38c6e0c7 709 (tarname (buffer-name))
e8421604
GM
710 (bufname (concat (file-name-nondirectory name)
711 " ("
6da7ea56 712 tarname
e8421604 713 ")"))
aa73f29c 714 (read-only-p (or buffer-read-only view-p))
38c6e0c7
AS
715 (new-buffer-file-name (expand-file-name
716 ;; `:' is not allowed on Windows
e8421604
GM
717 (concat tarname "!" name)))
718 (buffer (get-file-buffer new-buffer-file-name))
d26e6665 719 (just-created nil)
8e69f4d8
KH
720 (pos (point))
721 undo-list)
e8421604 722 (unless buffer
38c6e0c7 723 (setq buffer (generate-new-buffer bufname))
8e69f4d8
KH
724 (save-excursion
725 (set-buffer buffer)
726 (setq undo-list buffer-undo-list
727 buffer-undo-list t))
38c6e0c7 728 (setq bufname (buffer-name buffer))
aa73f29c
RS
729 (setq just-created t)
730 (unwind-protect
d26e6665
KH
731 (let (coding)
732 (narrow-to-region start end)
733 (goto-char start)
734 (setq coding (or coding-system-for-read
735 (and set-auto-coding-function
736 (funcall set-auto-coding-function
8e71d059 737 name (- end start)))
262be72a
MB
738 ;; The following binding causes
739 ;; find-buffer-file-type-coding-system
740 ;; (defined on dos-w32.el) to act as if
741 ;; the file being extracted existed, so
742 ;; that the file's contents' encoding and
743 ;; EOL format are auto-detected.
744 (let ((file-name-handler-alist
745 '(("" . tar-file-name-handler))))
746 (car (find-operation-coding-system
747 'insert-file-contents
748 (cons name (current-buffer)) t)))))
d26e6665
KH
749 (if (or (not coding)
750 (eq (coding-system-type coding) 'undecided))
751 (setq coding (detect-coding-region start end t)))
8f924df7
KH
752 (if (and default-enable-multibyte-characters
753 (coding-system-get coding :for-unibyte))
754 (save-excursion
755 (set-buffer buffer)
756 (set-buffer-multibyte nil)))
aa73f29c 757 (widen)
8f924df7 758 (decode-coding-region start end coding buffer)
aa73f29c
RS
759 (save-excursion
760 (set-buffer buffer)
cb33a0ce 761 (goto-char (point-min))
38c6e0c7 762 (setq buffer-file-name new-buffer-file-name)
fef5a0be
RS
763 (setq buffer-file-truename
764 (abbreviate-file-name buffer-file-name))
262be72a
MB
765 ;; Force buffer-file-coding-system to what
766 ;; decode-coding-region actually used.
767 (set-buffer-file-coding-system last-coding-system-used t)
fef5a0be 768 ;; Set the default-directory to the dir of the
f1180544 769 ;; superior buffer.
fef5a0be
RS
770 (setq default-directory
771 (save-excursion
772 (set-buffer tar-buffer)
773 default-directory))
aa73f29c 774 (normal-mode) ; pick a mode.
aa73f29c 775 (rename-buffer bufname)
0f8becaa
ER
776 (make-local-variable 'tar-superior-buffer)
777 (make-local-variable 'tar-superior-descriptor)
778 (setq tar-superior-buffer tar-buffer)
779 (setq tar-superior-descriptor descriptor)
f1180544 780 (setq buffer-read-only read-only-p)
76605277 781 (set-buffer-modified-p nil)
8e69f4d8 782 (setq buffer-undo-list undo-list)
76605277 783 (tar-subfile-mode 1))
aa73f29c 784 (set-buffer tar-buffer))
643415c4 785 (narrow-to-region (point-min) tar-header-offset)
d26e6665 786 (goto-char pos)))
aa73f29c 787 (if view-p
a51b80e7
MR
788 (view-buffer
789 buffer (and just-created 'kill-buffer-if-not-modified))
e865c5ce
RS
790 (if (eq other-window-p 'display)
791 (display-buffer buffer)
792 (if other-window-p
793 (switch-to-buffer-other-window buffer)
794 (switch-to-buffer buffer)))))))
aa73f29c
RS
795
796
797(defun tar-extract-other-window ()
8560523d 798 "In Tar mode, find this entry of the tar file in another window."
aa73f29c
RS
799 (interactive)
800 (tar-extract t))
801
e865c5ce 802(defun tar-display-other-window ()
8560523d 803 "In Tar mode, display this entry of the tar file in another window."
e865c5ce
RS
804 (interactive)
805 (tar-extract 'display))
806
aa73f29c 807(defun tar-view ()
8560523d 808 "In Tar mode, view the tar file entry on this line."
aa73f29c
RS
809 (interactive)
810 (tar-extract 'view))
811
812
813(defun tar-read-file-name (&optional prompt)
e865c5ce 814 "Read a file name with this line's entry as the default."
aa73f29c
RS
815 (or prompt (setq prompt "Copy to: "))
816 (let* ((default-file (expand-file-name
817 (tar-header-name (tar-desc-tokens
818 (tar-current-descriptor)))))
819 (target (expand-file-name
820 (read-file-name prompt
821 (file-name-directory default-file)
822 default-file nil))))
823 (if (or (string= "" (file-name-nondirectory target))
824 (file-directory-p target))
825 (setq target (concat (if (string-match "/$" target)
826 (substring target 0 (1- (match-end 0)))
827 target)
828 "/"
829 (file-name-nondirectory default-file))))
830 target))
831
832
833(defun tar-copy (&optional to-file)
8560523d 834 "In Tar mode, extract this entry of the tar file into a file on disk.
aa73f29c
RS
835If TO-FILE is not supplied, it is prompted for, defaulting to the name of
836the current tar-entry."
837 (interactive (list (tar-read-file-name)))
439fa06f 838 (let* ((descriptor (tar-get-descriptor))
aa73f29c
RS
839 (tokens (tar-desc-tokens descriptor))
840 (name (tar-header-name tokens))
841 (size (tar-header-size tokens))
643415c4
SM
842 (start (+ (tar-desc-data-start descriptor)
843 (- tar-header-offset (point-min))))
d4ec0200
RS
844 (end (+ start size))
845 (inhibit-file-name-handlers inhibit-file-name-handlers)
846 (inhibit-file-name-operation inhibit-file-name-operation))
6a608e8e
KH
847 (save-restriction
848 (widen)
d4ec0200
RS
849 ;; Inhibit compressing a subfile again if *both* name and
850 ;; to-file are handled by jka-compr
851 (if (and (eq (find-file-name-handler name 'write-region) 'jka-compr-handler)
852 (eq (find-file-name-handler to-file 'write-region) 'jka-compr-handler))
853 (setq inhibit-file-name-handlers
854 (cons 'jka-compr-handler
855 (and (eq inhibit-file-name-operation 'write-region)
856 inhibit-file-name-handlers))
857 inhibit-file-name-operation 'write-region))
d26e6665
KH
858 (let ((coding-system-for-write 'no-conversion))
859 (write-region start end to-file nil nil nil t)))
6a608e8e 860 (message "Copied tar entry %s to %s" name to-file)))
aa73f29c
RS
861
862(defun tar-flag-deleted (p &optional unflag)
8560523d 863 "In Tar mode, mark this sub-file to be deleted from the tar file.
aa73f29c
RS
864With a prefix argument, mark that many files."
865 (interactive "p")
866 (beginning-of-line)
8560523d 867 (dotimes (i (abs p))
aa73f29c
RS
868 (if (tar-current-descriptor unflag) ; barf if we're not on an entry-line.
869 (progn
870 (delete-char 1)
871 (insert (if unflag " " "D"))))
872 (forward-line (if (< p 0) -1 1)))
873 (if (eobp) nil (forward-char 36)))
874
875(defun tar-unflag (p)
8560523d 876 "In Tar mode, un-mark this sub-file if it is marked to be deleted.
aa73f29c
RS
877With a prefix argument, un-mark that many files forward."
878 (interactive "p")
879 (tar-flag-deleted p t))
880
881(defun tar-unflag-backwards (p)
8560523d 882 "In Tar mode, un-mark this sub-file if it is marked to be deleted.
aa73f29c
RS
883With a prefix argument, un-mark that many files backward."
884 (interactive "p")
885 (tar-flag-deleted (- p) t))
886
887
888(defun tar-expunge-internal ()
889 "Expunge the tar-entry specified by the current line."
890 (let* ((descriptor (tar-current-descriptor))
891 (tokens (tar-desc-tokens descriptor))
8560523d 892 ;; (line (tar-desc-data-start descriptor))
aa73f29c
RS
893 (name (tar-header-name tokens))
894 (size (tar-header-size tokens))
895 (link-p (tar-header-link-type tokens))
896 (start (tar-desc-data-start descriptor))
897 (following-descs (cdr (memq descriptor tar-parse-info))))
898 (if link-p (setq size 0)) ; size lies for hard-links.
899 ;;
900 ;; delete the current line...
901 (beginning-of-line)
902 (let ((line-start (point)))
903 (end-of-line) (forward-char)
8560523d
SM
904 ;; decrement the header-pointer to be in sync...
905 (setq tar-header-offset (- tar-header-offset (- (point) line-start)))
906 (delete-region line-start (point)))
aa73f29c
RS
907 ;;
908 ;; delete the data pointer...
909 (setq tar-parse-info (delq descriptor tar-parse-info))
910 ;;
911 ;; delete the data from inside the file...
912 (widen)
8560523d 913 (let* ((data-start (+ start (- tar-header-offset (point-min)) -512))
aa73f29c
RS
914 (data-end (+ data-start 512 (ash (ash (+ size 511) -9) 9))))
915 (delete-region data-start data-end)
916 ;;
917 ;; and finally, decrement the start-pointers of all following
918 ;; entries in the archive. This is a pig when deleting a bunch
919 ;; of files at once - we could optimize this to only do the
920 ;; iteration over the files that remain, or only iterate up to
921 ;; the next file to be deleted.
922 (let ((data-length (- data-end data-start)))
048d06bd 923 (dolist (desc following-descs)
aa73f29c
RS
924 (tar-setf (tar-desc-data-start desc)
925 (- (tar-desc-data-start desc) data-length))))
926 ))
643415c4 927 (narrow-to-region (point-min) tar-header-offset))
aa73f29c
RS
928
929
930(defun tar-expunge (&optional noconfirm)
8560523d 931 "In Tar mode, delete all the archived files flagged for deletion.
aa73f29c
RS
932This does not modify the disk image; you must save the tar file itself
933for this to be permanent."
934 (interactive)
935 (if (or noconfirm
67501c01 936 (y-or-n-p "Expunge files marked for deletion? "))
d26e6665 937 (let ((n 0))
aa73f29c 938 (save-excursion
cb33a0ce 939 (goto-char (point-min))
aa73f29c
RS
940 (while (not (eobp))
941 (if (looking-at "D")
942 (progn (tar-expunge-internal)
943 (setq n (1+ n)))
944 (forward-line 1)))
945 ;; after doing the deletions, add any padding that may be necessary.
946 (tar-pad-to-blocksize)
643415c4 947 (narrow-to-region (point-min) tar-header-offset))
aa73f29c 948 (if (zerop n)
67501c01
RS
949 (message "Nothing to expunge.")
950 (message "%s files expunged. Be sure to save this buffer." n)))))
aa73f29c
RS
951
952
953(defun tar-clear-modification-flags ()
e865c5ce 954 "Remove the stars at the beginning of each line."
a7e5e8cb 955 (interactive)
aa73f29c 956 (save-excursion
cb33a0ce 957 (goto-char (point-min))
d26e6665 958 (while (< (point) tar-header-offset)
e5d56b61 959 (if (not (eq (following-char) ?\s))
aa73f29c
RS
960 (progn (delete-char 1) (insert " ")))
961 (forward-line 1))))
962
963
964(defun tar-chown-entry (new-uid)
8560523d 965 "Change the user-id associated with this entry in the tar file.
aa73f29c
RS
966If this tar file was written by GNU tar, then you will be able to edit
967the user id as a string; otherwise, you must edit it as a number.
968You can force editing as a number by calling this with a prefix arg.
969This does not modify the disk image; you must save the tar file itself
970for this to be permanent."
971 (interactive (list
972 (let ((tokens (tar-desc-tokens (tar-current-descriptor))))
973 (if (or current-prefix-arg
974 (not (tar-header-magic tokens)))
975 (let (n)
976 (while (not (numberp (setq n (read-minibuffer
977 "New UID number: "
978 (format "%s" (tar-header-uid tokens)))))))
979 n)
980 (read-string "New UID string: " (tar-header-uname tokens))))))
981 (cond ((stringp new-uid)
982 (tar-setf (tar-header-uname (tar-desc-tokens (tar-current-descriptor)))
983 new-uid)
984 (tar-alter-one-field tar-uname-offset (concat new-uid "\000")))
985 (t
986 (tar-setf (tar-header-uid (tar-desc-tokens (tar-current-descriptor)))
987 new-uid)
988 (tar-alter-one-field tar-uid-offset
989 (concat (substring (format "%6o" new-uid) 0 6) "\000 ")))))
990
991
992(defun tar-chgrp-entry (new-gid)
8560523d 993 "Change the group-id associated with this entry in the tar file.
aa73f29c
RS
994If this tar file was written by GNU tar, then you will be able to edit
995the group id as a string; otherwise, you must edit it as a number.
996You can force editing as a number by calling this with a prefix arg.
997This does not modify the disk image; you must save the tar file itself
998for this to be permanent."
999 (interactive (list
1000 (let ((tokens (tar-desc-tokens (tar-current-descriptor))))
1001 (if (or current-prefix-arg
1002 (not (tar-header-magic tokens)))
1003 (let (n)
1004 (while (not (numberp (setq n (read-minibuffer
1005 "New GID number: "
1006 (format "%s" (tar-header-gid tokens)))))))
1007 n)
1008 (read-string "New GID string: " (tar-header-gname tokens))))))
1009 (cond ((stringp new-gid)
1010 (tar-setf (tar-header-gname (tar-desc-tokens (tar-current-descriptor)))
1011 new-gid)
1012 (tar-alter-one-field tar-gname-offset
1013 (concat new-gid "\000")))
1014 (t
1015 (tar-setf (tar-header-gid (tar-desc-tokens (tar-current-descriptor)))
1016 new-gid)
1017 (tar-alter-one-field tar-gid-offset
1018 (concat (substring (format "%6o" new-gid) 0 6) "\000 ")))))
1019
1020(defun tar-rename-entry (new-name)
8560523d 1021 "Change the name associated with this entry in the tar file.
aa73f29c
RS
1022This does not modify the disk image; you must save the tar file itself
1023for this to be permanent."
1024 (interactive
1025 (list (read-string "New name: "
1026 (tar-header-name (tar-desc-tokens (tar-current-descriptor))))))
439fa06f 1027 (if (string= "" new-name) (error "zero length name"))
d26e6665
KH
1028 (let ((encoded-new-name (encode-coding-string new-name
1029 tar-file-name-coding-system)))
1030 (if (> (length encoded-new-name) 98) (error "name too long"))
1031 (tar-setf (tar-header-name (tar-desc-tokens (tar-current-descriptor)))
1032 new-name)
1033 (tar-alter-one-field 0
1034 (substring (concat encoded-new-name (make-string 99 0)) 0 99))))
aa73f29c
RS
1035
1036
1037(defun tar-chmod-entry (new-mode)
c40d3365 1038 "Change the protection bits associated with this entry in the tar file.
aa73f29c
RS
1039This does not modify the disk image; you must save the tar file itself
1040for this to be permanent."
1041 (interactive (list (tar-parse-octal-integer-safe
1042 (read-string "New protection (octal): "))))
1043 (tar-setf (tar-header-mode (tar-desc-tokens (tar-current-descriptor)))
1044 new-mode)
1045 (tar-alter-one-field tar-mode-offset
1046 (concat (substring (format "%6o" new-mode) 0 6) "\000 ")))
1047
1048
1049(defun tar-alter-one-field (data-position new-data-string)
1050 (let* ((descriptor (tar-current-descriptor))
d26e6665 1051 (tokens (tar-desc-tokens descriptor)))
aa73f29c
RS
1052 (unwind-protect
1053 (save-excursion
1054 ;;
1055 ;; update the header-line.
1056 (beginning-of-line)
1057 (let ((p (point)))
1058 (forward-line 1)
1059 (delete-region p (point))
1bc28099 1060 (insert (tar-header-block-summarize tokens) "\n")
d26e6665 1061 (setq tar-header-offset (point-max)))
f1180544 1062
aa73f29c 1063 (widen)
c40d3365
MB
1064 (let* ((start (+ (tar-desc-data-start descriptor)
1065 (- tar-header-offset (point-min))
1066 -512)))
aa73f29c
RS
1067 ;;
1068 ;; delete the old field and insert a new one.
1069 (goto-char (+ start data-position))
1070 (delete-region (point) (+ (point) (length new-data-string))) ; <--
d26e6665
KH
1071
1072 ;; As new-data-string is unibyte, just inserting it will
1073 ;; make eight-bit chars to the corresponding multibyte
1074 ;; chars. This avoid that conversion, i.e., eight-bit
1075 ;; chars are converted to multibyte form of eight-bit
1076 ;; chars.
1077 (insert (string-to-multibyte new-data-string))
aa73f29c
RS
1078 ;;
1079 ;; compute a new checksum and insert it.
1bc28099 1080 (let ((chk (tar-header-block-checksum
aa73f29c
RS
1081 (buffer-substring start (+ start 512)))))
1082 (goto-char (+ start tar-chk-offset))
1083 (delete-region (point) (+ (point) 8))
1084 (insert (format "%6o" chk))
1085 (insert 0)
1086 (insert ? )
1087 (tar-setf (tar-header-checksum tokens) chk)
1088 ;;
1089 ;; ok, make sure we didn't botch it.
d246db94 1090 (tar-header-block-check-checksum
aa73f29c
RS
1091 (buffer-substring start (+ start 512))
1092 chk (tar-header-name tokens))
1093 )))
643415c4 1094 (narrow-to-region (point-min) tar-header-offset)
47414bc1 1095 (tar-next-line 0))))
aa73f29c
RS
1096
1097
3d7fc2fb
ER
1098(defun tar-octal-time (timeval)
1099 ;; Format a timestamp as 11 octal digits. Ghod, I hope this works...
1100 (let ((hibits (car timeval)) (lobits (car (cdr timeval))))
516f7fca
EZ
1101 (format "%05o%01o%05o"
1102 (lsh hibits -2)
1103 (logior (lsh (logand 3 hibits) 1)
1104 (if (> (logand lobits 32768) 0) 1 0))
1105 (logand 32767 lobits)
1106 )))
3d7fc2fb 1107
aa73f29c 1108(defun tar-subfile-save-buffer ()
e865c5ce
RS
1109 "In tar subfile mode, save this buffer into its parent tar-file buffer.
1110This doesn't write anything to disk; you must save the parent tar-file buffer
aa73f29c
RS
1111to make your changes permanent."
1112 (interactive)
0f8becaa 1113 (if (not (and (boundp 'tar-superior-buffer) tar-superior-buffer))
439fa06f 1114 (error "This buffer has no superior tar file buffer"))
0f8becaa 1115 (if (not (and (boundp 'tar-superior-descriptor) tar-superior-descriptor))
439fa06f 1116 (error "This buffer doesn't have an index into its superior tar file!"))
aa73f29c
RS
1117 (save-excursion
1118 (let ((subfile (current-buffer))
47414bc1 1119 (coding buffer-file-coding-system)
1b33e237
KH
1120 (descriptor tar-superior-descriptor)
1121 subfile-size)
0f8becaa 1122 (set-buffer tar-superior-buffer)
aa73f29c
RS
1123 (let* ((tokens (tar-desc-tokens descriptor))
1124 (start (tar-desc-data-start descriptor))
1125 (name (tar-header-name tokens))
1126 (size (tar-header-size tokens))
1127 (size-pad (ash (ash (+ size 511) -9) 9))
1128 (head (memq descriptor tar-parse-info))
d26e6665 1129 (following-descs (cdr head)))
aa73f29c
RS
1130 (if (not head)
1131 (error "Can't find this tar file entry in its parent tar file!"))
1132 (unwind-protect
1133 (save-excursion
aa73f29c 1134 ;; delete the old data...
643415c4 1135 (let* ((data-start (+ start (- tar-header-offset (point-min))))
aa73f29c 1136 (data-end (+ data-start (ash (ash (+ size 511) -9) 9))))
d26e6665
KH
1137 (narrow-to-region data-start data-end)
1138 (delete-region (point-min) (point-max))
aa73f29c
RS
1139 ;; insert the new data...
1140 (goto-char data-start)
d26e6665
KH
1141 (save-excursion
1142 (set-buffer subfile)
1143 (save-restriction
1144 (widen)
1145 (encode-coding-region 1 (point-max) coding tar-superior-buffer)))
1146 (setq subfile-size (- (point-max) (point-min)))
aa73f29c
RS
1147 ;;
1148 ;; pad the new data out to a multiple of 512...
1149 (let ((subfile-size-pad (ash (ash (+ subfile-size 511) -9) 9)))
d26e6665 1150 (goto-char (point-max))
aa73f29c
RS
1151 (insert (make-string (- subfile-size-pad subfile-size) 0))
1152 ;;
1153 ;; update the data pointer of this and all following files...
1154 (tar-setf (tar-header-size tokens) subfile-size)
1155 (let ((difference (- subfile-size-pad size-pad)))
048d06bd 1156 (dolist (desc following-descs)
aa73f29c
RS
1157 (tar-setf (tar-desc-data-start desc)
1158 (+ (tar-desc-data-start desc) difference))))
1159 ;;
1160 ;; Update the size field in the header block.
d26e6665 1161 (widen)
aa73f29c
RS
1162 (let ((header-start (- data-start 512)))
1163 (goto-char (+ header-start tar-size-offset))
1164 (delete-region (point) (+ (point) 12))
1165 (insert (format "%11o" subfile-size))
1166 (insert ? )
1167 ;;
1168 ;; Maybe update the datestamp.
1169 (if (not tar-update-datestamp)
1170 nil
1171 (goto-char (+ header-start tar-time-offset))
1172 (delete-region (point) (+ (point) 12))
3d7fc2fb 1173 (insert (tar-octal-time (current-time)))
aa73f29c
RS
1174 (insert ? ))
1175 ;;
1176 ;; compute a new checksum and insert it.
1bc28099 1177 (let ((chk (tar-header-block-checksum
aa73f29c
RS
1178 (buffer-substring header-start data-start))))
1179 (goto-char (+ header-start tar-chk-offset))
1180 (delete-region (point) (+ (point) 8))
1181 (insert (format "%6o" chk))
1182 (insert 0)
1183 (insert ? )
1184 (tar-setf (tar-header-checksum tokens) chk)))
1185 ;;
1186 ;; alter the descriptor-line...
1187 ;;
1188 (let ((position (- (length tar-parse-info) (length head))))
cb33a0ce 1189 (goto-char (point-min))
97546017 1190 (forward-line position)
aa73f29c
RS
1191 (beginning-of-line)
1192 (let ((p (point))
97590737 1193 after
aa73f29c
RS
1194 (m (set-marker (make-marker) tar-header-offset)))
1195 (forward-line 1)
97590737
RS
1196 (setq after (point))
1197 ;; Insert the new text after the old, before deleting,
1198 ;; to preserve the window start.
1b33e237 1199 (let ((line (tar-header-block-summarize tokens t)))
d26e6665 1200 (insert-before-markers line "\n"))
97590737 1201 (delete-region p after)
aa73f29c
RS
1202 (setq tar-header-offset (marker-position m)))
1203 )))
1204 ;; after doing the insertion, add any final padding that may be necessary.
1205 (tar-pad-to-blocksize))
8f924df7 1206 (narrow-to-region (point-min) tar-header-offset)))
aa73f29c 1207 (set-buffer-modified-p t) ; mark the tar file as modified
47414bc1 1208 (tar-next-line 0)
aa73f29c
RS
1209 (set-buffer subfile)
1210 (set-buffer-modified-p nil) ; mark the tar subfile as unmodified
67501c01 1211 (message "Saved into tar-buffer `%s'. Be sure to save that buffer!"
0f8becaa 1212 (buffer-name tar-superior-buffer))
47414bc1
RS
1213 ;; Prevent basic-save-buffer from changing our coding-system.
1214 (setq last-coding-system-used buffer-file-coding-system)
e865c5ce
RS
1215 ;; Prevent ordinary saving from happening.
1216 t)))
aa73f29c
RS
1217
1218
1b33e237 1219;; When this function is called, it is sure that the buffer is unibyte.
aa73f29c
RS
1220(defun tar-pad-to-blocksize ()
1221 "If we are being anal about tar file blocksizes, fix up the current buffer.
1222Leaves the region wide."
1223 (if (null tar-anal-blocksize)
1224 nil
1225 (widen)
1226 (let* ((last-desc (nth (1- (length tar-parse-info)) tar-parse-info))
1227 (start (tar-desc-data-start last-desc))
1228 (tokens (tar-desc-tokens last-desc))
1229 (link-p (tar-header-link-type tokens))
1230 (size (if link-p 0 (tar-header-size tokens)))
1231 (data-end (+ start size))
1232 (bbytes (ash tar-anal-blocksize 9))
8560523d 1233 (pad-to (+ bbytes (* bbytes (/ (- data-end (point-min)) bbytes))))
628d6cef 1234 (inhibit-read-only t) ; ##
aa73f29c
RS
1235 )
1236 ;; If the padding after the last data is too long, delete some;
1237 ;; else insert some until we are padded out to the right number of blocks.
1238 ;;
8560523d
SM
1239 (let ((goal-end (+ (or tar-header-offset 0) pad-to)))
1240 (if (> (point-max) goal-end)
1241 (delete-region goal-end (point-max))
1242 (goto-char (point-max))
1243 (insert (make-string (- goal-end (point-max)) ?\0)))))))
aa73f29c
RS
1244
1245
439fa06f 1246;; Used in write-file-hook to write tar-files out correctly.
67501c01
RS
1247(defun tar-mode-write-file ()
1248 (unwind-protect
1249 (save-excursion
1250 (widen)
1251 ;; Doing this here confuses things - the region gets left too wide!
1252 ;; I suppose this is run in a context where changing the buffer is bad.
1253 ;; (tar-pad-to-blocksize)
4063eb1c
DL
1254 ;; tar-header-offset turns out to be null for files fetched with W3,
1255 ;; at least.
1b33e237 1256 (let ((coding-system-for-write 'no-conversion))
d26e6665
KH
1257 (write-region (or tar-header-offset
1258 (point-min))
1b33e237
KH
1259 (point-max)
1260 buffer-file-name nil t))
87aed91b
RS
1261 (tar-clear-modification-flags)
1262 (set-buffer-modified-p nil))
8f924df7 1263 (narrow-to-region (point-min) tar-header-offset))
23bdfc9a 1264 ;; Return t because we've written the file.
67501c01 1265 t)
aa73f29c 1266\f
aa73f29c
RS
1267(provide 'tar-mode)
1268
8560523d 1269;; arch-tag: 8a585a4a-340e-42c2-89e7-d3b1013a4b78
c88ab9ce 1270;;; tar-mode.el ends here