-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathbashcpio
executable file
·309 lines (245 loc) · 7.35 KB
/
bashcpio
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
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
#!/bin/bash
# Copyright 2016 Fred Clift. See accompanying LICENSE file for details.
umask 0022
#pointer to beginning of current file to be extracted
declare -i current_base=0
declare pkg
declare tmpfile
declare -A hardlinks
#some other (integer) globals
declare -i filesize
declare -i namesize
declare -i fileperm
declare -i filetype
declare -i mtime
# parse command line, make temp file from input stream if no package given
# since we can only operate on real files. Print usage if user gives
# any command line args
function cmd_line () {
while (( "$#" )); do
if [[ "$1" = "-"* ]]; then
echo "Commandline options not supported. operating in -idvu mode"
echo " In other words, extract into directories, overwrite, and print names"
echo " Usage: bashcpio < archive "
echo " rpm2cpio.sh foo.rpm | bashcpio"
echo " bashcpio archive"
exit 1
else
pkg="$1"
fi
shift
done
# remember that we made temp file so we can remove it at exit later
if [ -z "$pkg" ]; then
pkg=./bashcpio.tmp.$RANDOM
tmpfile="x"
dd > $pkg 2>/dev/null
fi
# does it exist and can we read it?
if [ ! -r "$pkg" ]; then
echo "No such file: $pkg"
exit
fi
}
# bdirname
# returns the dirname of a dirname/path string
# replacement for C binary
# thanks to /u/LukeShu on Reddit for this code
function bdirname () {
local file=$1
local dir
if [[ "$file" = */* ]]; then
dir="${file%/*}"
if [ -z "$dir" ]; then
dir=/
fi
else
dir=.
fi
printf '%s\n' "$dir"
}
# parent_dir
# makes parent directory (if needed) for current fname
function parent_dir () {
dir=$( bdirname "$fname" )
# TODO: ownerhsip of parent dirs not specified in archive? undefined in cpio spec?
# its a little faster to [ ! -d $dir ] than to let mkdir -p fail silently
if [ ! -d "$dir" ]; then
mkdir -p "$dir"
fi
}
# fixup_atts
# set mode, owner group mtime on file
function fixup_atts () {
#only works if running as root - silently ignore errors
chown -f -h "$uid":"$gid" "$fname"
chmod -f "$fileperm" "$fname"
#convert epoch time to yyyymmddhhmm.ss for touch
# thanks to /u/geihra on reddit - got rid of external date command
printf -v ds '%(%Y%m%d%H%M.%S)T' "$mtime"
touch -h -c -m -t "$ds" "$fname"
}
# padding insize
# giant PITA getting this padding right. math in bash is a chore
declare padding_return=""
function padding () {
local base=$1
local insize=$2
local sb
sb=$(( base + insize ))
sb=$(( sb + (( 4 - ( sb % 4 )) % 4 ) ))
padding_return="$sb"
}
# extract_file namesize
# extractor for a file in an archive
# saves the current file, sets perms, uid,gid ,mtime, if possible
function extract_file () {
local sb
local dir
local ds
#sb bytes (skipped) will now get us to beginning of file contents
# header size = 13*8 + 6 + filenamesize (including 1 null byte)
# + null padding to make the whole thing be a multiple of 4
padding 110 "$namesize"
sb="$padding_return"
parent_dir
if [ "$nlink" -gt 1 ]; then
#first one? save so we can link the others to it
if [ -z "${hardlinks[$ino]}" ]; then
hardlinks[$ino]=$fname
else
ln -f "${hardlinks[$ino]}" "$fname"
fi
fi
# TODO: chroot here maybe, or some sanitization of path name
get_chunk "$sb" "$filesize" "$fname"
fixup_atts
}
# extractor for a directory in an archive
# extract_dir
# extracts directory and sets perms, uid, gid, mtime if possible
function extract_dir () {
mkdir -p "$fname"
fixup_atts
# unless the creator of the archive put the directory entry last
# in the archive, unpacking files into the dir will update mtime.
# To do this right, you need to save directory mtime settings
# and do them from the leaves up.
# TODO queue up dir-mtime settings and order them right. ugh
}
#symlink has target in contents
function extract_symlink () {
local sb
#sb bytes (skipped) will now get us to beginning of file contents
# header size = 13*8 + 6 + filenamesize (including 1 null byte)
# + null padding to make the whole thing be a multiple of 4
padding 110 "$namesize"
sb="$padding_return"
get_chunk "$sb" "$filesize"
parent_dir
ln -f -s "$get_chunk_return" "$fname"
fixup_atts
}
# get_chunk(skipbytes, chunklen, [savefile])
# return value in get_chunk_return or saved into savefile
# if savefile is specified
# note the use of the global current_base - points to the current file record
# so that this code can be used on any file in the archive
declare get_chunk_return=""
function get_chunk () {
local skp
local siz
local file
local cmd
declare -i skp="$1"+"$current_base"
siz="$2"
# write to file if specified, else write to stdout
if [ -n "$3" ]; then
file="of=""$3"
else
file=""
fi
printf -v cmd "dd if=%s count=%s skip=%s %s iflag=count_bytes,skip_bytes" "$pkg" "$siz" "$skp" "$file"
get_chunk_return=$( $cmd 2>/dev/null )
}
# read header of current record using fixed offsets
# set a bunch of global variables
function read_header () {
#grab what should be the whole header (not including variable filename)
get_chunk 0 110; rec="$get_chunk_return"
# validate magic immediately to minimize the possiblity of wierd failure
# with non-ascii content
magic="${rec:0:6}"
check_magic
ino="${rec:6:8}"
mode="${rec:14:8}"
uid="${rec:22:8}"
gid="${rec:30:8}"
nlink="${rec:38:8}"
mtime="0x${rec:46:8}"
filesize="0x${rec:54:8}"
#devmajor="${rec:62:8}"
#devminor="${rec:70:8}"
#rdevmajor="${rec:78:8}"
#rdevminor="${rec:86:8}"
namesize="0x${rec:94:8}"
#check="${rec:102:8}"
get_chunk 110 "$namesize"; fname="$get_chunk_return"
#mask and convert to octal
fileperm=$(printf "%o" "0x${mode:5}")
#extract filetype section
filetype=0x${mode:0:5}
}
#verifies that we read a supported record type
function check_magic () {
if [ "$magic" != "070701" ] && [ "$magic" != "070702" ] ; then
echo "Invalid header - not a cpio archive?"
echo "$magic"
exit 1
fi
return 0
}
# everything to extract a file out of a cpio archive
# as pointed at by $current_base
function get_one_file () {
local sb
read_header
if [ "$fname" = 'TRAILER!!!' ]; then
#end of archive
return 1
fi
# only handle files, directories, symlinks and hardlinks
# devices? fifos?
echo "x: $fname"
case "$filetype" in
8)
extract_file ;;
4)
extract_dir ;;
10)
extract_symlink ;;
*)
#should this be a fatal error?
echo "Unsupported filetype: $filetype for $fname"
esac
#header + filename + padding
padding 110 "$namesize"
sb="$padding_return"
# + file payload + padding
padding "$sb" "$filesize"
sb="$padding_return"
current_base="$current_base"+"$sb"
return 0
}
function cleanup () {
if [ -n "$tmpfile" ]; then
rm -f "$pkg"
fi
}
trap cleanup EXIT
cmd_line "$1"
fin=0;
until [ "$fin" = 1 ]; do
get_one_file
fin="$?"
done