-
Notifications
You must be signed in to change notification settings - Fork 1
/
archive-cpio.el
209 lines (187 loc) · 8.48 KB
/
archive-cpio.el
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
;;; archive-cpio.el --- CPIO support for archive-mode -*- lexical-binding: t; -*-
;; Copyright (C) 2012 Stefan Monnier
;; Copyright (C) 2017 Magnus Henoch
;; Author: Magnus Henoch <[email protected]>
;; Keywords: files
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;; This module adds support for CPIO archives to archive-mode.
;; The format supported is the "New ASCII Format" as documented in
;; http://people.freebsd.org/~kientzle/libarchive/man/cpio.5.txt.
;; Based on cpio-mode.el by Stefan Monnier, posted here:
;; https://lists.gnu.org/archive/html/emacs-devel/2015-04/msg00824.html
;;; Code:
(require 'arc-mode)
(require 'cl-lib)
(defconst archive-cpio-entry-header-re
"07070[12]\\([[:xdigit:]]\\{8\\}\\)\\([[:xdigit:]]\\{8\\}\\)\\([[:xdigit:]]\\{8\\}\\)\\([[:xdigit:]]\\{8\\}\\)\\([[:xdigit:]]\\{8\\}\\)\\([[:xdigit:]]\\{8\\}\\)\\([[:xdigit:]]\\{8\\}\\)\\([[:xdigit:]]\\{8\\}\\)\\([[:xdigit:]]\\{8\\}\\)\\([[:xdigit:]]\\{8\\}\\)\\([[:xdigit:]]\\{8\\}\\)\\([[:xdigit:]]\\{8\\}\\)[[:xdigit:]]\\{8\\}\\|\0+\\'"
"Regular expression matching a CPIO entry.
The matched groups are:
1. ino
2. mode
3. uid
4. gid
5. nlink
6. mtime
7. filesize
8. devmajor
9. devminor
10. rdevmajor
11. rdevminor
12. namesize
The name starts at the end of the match, and goes on for namesize
bytes. It is padded with NUL bytes so that the start of file
data is aligned to four bytes. File data is also padded, so that
the next header is aligned to four bytes.")
;;;###autoload
(defun archive-cpio-find-type ()
"Return `cpio' if the current buffer contains a CPIO archive.
Otherwise, return nil.
This function is meant to be used as a :before-until advice for
`archive-find-type'."
(widen)
(goto-char (point-min))
(when (looking-at archive-cpio-entry-header-re)
'cpio))
;;;###autoload
(with-eval-after-load "arc-mode"
(advice-add 'archive-find-type :before-until 'archive-cpio-find-type))
(defun archive-cpio--parse-mode (mode)
"Parse MODE, an integer, and return a permissions string (10 characters)."
(string
(cl-case (logand #o170000 mode)
(#o140000 ?s)
(#o120000 ?l)
(#o100000 ?-)
(#o060000 ?b)
(#o040000 ?d)
(#o020000 ?c)
(#o010000 ?p)
(t (error "Unknown mode %S" mode)))
(if (zerop (logand #o400 mode)) ?- ?r)
(if (zerop (logand #o200 mode)) ?- ?w)
(if (zerop (logand #o100 mode))
(if (zerop (logand #o4000 mode)) ?- ?S)
(if (zerop (logand #o4000 mode)) ?x ?s))
(if (zerop (logand #o040 mode)) ?- ?r)
(if (zerop (logand #o020 mode)) ?- ?w)
(if (zerop (logand #o010 mode))
(if (zerop (logand #o2000 mode)) ?- ?S)
(if (zerop (logand #o2000 mode)) ?x ?s))
(if (zerop (logand #o004 mode)) ?- ?r)
(if (zerop (logand #o002 mode)) ?- ?w)
(if (zerop (logand #o001 mode)) ?- ?x)))
(defun archive-cpio-summarize (&optional archive-buffer)
"Summarize files in a cpio archive.
Insert file list in ARCHIVE-BUFFER, or the current buffer if
ARCHIVE-BUFFER is nil."
(let (visual files)
(goto-char (point-min))
(while (not (eobp))
(cl-assert (zerop (mod (- (point) (point-min)) 4)))
(cond
((not (looking-at archive-cpio-entry-header-re))
(error "Unrecognized cpio header format"))
((not (match-beginning 1))
;; Reached the trailing padding, just skip it.
;; (put-text-property (point) (point-max) 'invisible t)
(goto-char (match-end 0)))
(t
(let* ((ino (string-to-number (match-string 1) 16))
(mode (string-to-number (match-string 2) 16))
(uid (string-to-number (match-string 3) 16))
(gid (string-to-number (match-string 4) 16))
;; (nlink (string-to-number (match-string 5) 16))
;; (mtime (string-to-number (match-string 6) 16))
(filesize (string-to-number (match-string 7) 16))
;; (devmajor (string-to-number (match-string 8) 16))
;; (devminor (string-to-number (match-string 9) 16))
;; (rdevmajor (string-to-number (match-string 10) 16))
;; (rdevminor (string-to-number (match-string 11) 16))
(namesize (string-to-number (match-string 12) 16))
(namebeg (match-end 0))
(name (buffer-substring namebeg (+ namebeg namesize -1)))
(filebeg (+ (match-end 0) 2 (* (/ (+ namesize 1) 4) 4)))
(next (+ filebeg (* (/ (+ filesize 3) 4) 4))))
(if (and (zerop ino) (zerop mode) (zerop filesize)
(equal name "TRAILER!!!"))
;; Last entry in archive: go to end
(goto-char (point-max))
;; Building this in two parts, since we need to know in
;; which column the file name starts.
(let* ((text-a
(format " %s %8.0f %10d/%-10d "
(archive-cpio--parse-mode mode)
filesize
uid
gid))
(text-b
(format "%s%s"
name
(if (= (logand #o170000 mode) #o120000) ;Symlink
(concat " -> " (buffer-substring filebeg (+ filebeg filesize)))
"")))
(text (concat text-a text-b)))
(if (< emacs-major-version 28)
(progn
(push (vector text (length text-a) (length text)) visual)
(push (vector name name nil mode filebeg) files))
(push (archive--file-summary text (length text-a) (length text)) visual)
(push (archive--file-desc name name mode filesize nil :pos filebeg) files))))
(goto-char next)))))
(with-current-buffer (or archive-buffer (current-buffer))
(goto-char (point-min))
(insert "M Filemode Length UID/GID File\n")
(insert "- ---------- -------- ---------- ---------- -----\n")
(archive-summarize-files (nreverse visual))
(apply #'vector (nreverse files)))))
(defun archive-cpio-extract (archive name)
"Open the CPIO file ARCHIVE and extract NAME into the current buffer.
This function is meant to be called from `archive-extract'."
;; Based on archive-ar-extract
(let ((destbuf (current-buffer))
(archivebuf (find-file-noselect archive)))
(archive-cpio-extract-from-buffer name archivebuf destbuf)))
(defun archive-cpio-extract-from-buffer (name archivebuf destbuf)
"Extract the file named NAME from the archive in ARCHIVEBUF.
Insert the file contents into DESTBUF."
(with-current-buffer archivebuf
(let ((from nil) size)
(save-restriction
;; We may be in archive-mode or not, so either with or without
;; narrowing and with or without a prepended summary.
(save-excursion
(widen)
(search-forward-regexp archive-cpio-entry-header-re)
(goto-char (match-beginning 0))
(while (and (not from) (looking-at archive-cpio-entry-header-re))
(let* ((namesize (string-to-number (match-string 12) 16))
(filesize (string-to-number (match-string 7) 16))
(namebeg (match-end 0))
(entry-name (buffer-substring namebeg (+ namebeg namesize -1)))
(filebeg (+ (match-end 0) 2 (* (/ (+ namesize 1) 4) 4)))
(next (+ filebeg (* (/ (+ filesize 3) 4) 4))))
(if (equal entry-name name)
(setq from filebeg
size filesize)
;; Move to the end of the data.
(goto-char next)))))
(when from
(set-buffer-multibyte nil)
(with-current-buffer destbuf
;; Do it within the `widen'.
(insert-buffer-substring archivebuf from (+ from size)))
(set-buffer-multibyte 'to)
;; Inform the caller that the call succeeded.
t)))))
(provide 'archive-cpio)
;;; archive-cpio.el ends here