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