-
-
Notifications
You must be signed in to change notification settings - Fork 187
/
Copy pathunpack_initramfs.sh
executable file
·145 lines (132 loc) · 4.39 KB
/
unpack_initramfs.sh
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
#! /bin/bash
set -e -o pipefail
. /etc/functions
TRACE_FUNC
# Unpack a Linux initramfs archive.
#
# In general, the initramfs archive is one or more cpio archives, optionally
# compressed, concatenated together. Uncompressed and compressed segments can
# exist in the same file. Zero bytes between segments are skipped. To properly
# unpack such an archive, all segments must be unpacked.
#
# This script unpacks such an archive, but with a limitation that once a
# compressed segment is reached, no more segments can be read. This works for
# common initrds on x86, where the microcode must be stored in an initial
# uncompressed segment, followed by the "real" initramfs content which is
# usually in one compressed segment.
#
# The limitation comes from gunzip/unzstd, there's no way to prevent them from
# consuming trailing data or tell us the member/frame length. The script
# succeeds with whatever was extracted, since this is used to extract particular
# files and boot can proceed as long as those files were found.
INITRAMFS_ARCHIVE="$1"
DEST_DIR="$2"
shift
shift
# rest of args go to cpio, can specify filename patterns
CPIO_ARGS=("$@")
# Consume zero bytes, the first nonzero byte read (if any) is repeated on stdout
consume_zeros() {
TRACE_FUNC
next_byte='00'
while [ "$next_byte" = "00" ]; do
# if we reach EOF, next_byte becomes empty (dd does not fail)
next_byte="$(dd bs=1 count=1 status=none | xxd -p | tr -d ' ')"
done
# if we finished due to nonzero byte (not EOF), then carry that byte
if [ -n "$next_byte" ]; then
echo -n "$next_byte" | xxd -p -r
fi
}
unpack_cpio() {
TRACE_FUNC
(
cd "$dest_dir"
cpio -i "${CPIO_ARGS[@]}" 2>/dev/null
)
}
# unpack the first segment of an archive, then write the rest to another file
unpack_first_segment() {
TRACE_FUNC
unpack_archive="$1"
dest_dir="$2"
rest_archive="$3"
mkdir -p "$dest_dir"
# peek the beginning of the file to determine what type of content is next
magic="$(dd if="$unpack_archive" bs=6 count=1 status=none 2>/dev/null | xxd -p)"
# read this segment of the archive, then write the rest to the next file
(
# Magic values correspond to Linux init/initramfs.c (zero, cpio) and
# lib/decompress.c (gzip)
case "$magic" in
00*)
DEBUG "archive segment $magic: uncompressed cpio"
# Skip zero bytes and copy the first nonzero byte
consume_zeros
# Copy the remaining data
cat
;;
303730373031* | 303730373032*) # plain cpio
DEBUG "archive segment $magic: plain cpio"
# Unpack the plain cpio, this stops reading after the trailer
unpack_cpio
# Copy the remaining data
cat
;;
1f8b* | 1f9e*) # gzip
DEBUG "archive segment $magic: gzip"
# gunzip won't stop when reaching the end of the gzipped member,
# so we can't read another segment after this. We can't
# reasonably determine the member length either, this requires
# walking all the compressed blocks.
gunzip | unpack_cpio
;;
fd37*) # xz
DEBUG "archive segment $magic: xz"
unxz | unpack_cpio
;;
28b5*) # zstd
DEBUG "archive segment $magic: zstd"
# Like gunzip, this will not stop when reaching the end of the
# frame, and determining the frame length requires walking all
# of its blocks.
(zstd-decompress -d || true) | unpack_cpio
;;
*) # unknown
die "Can't decompress initramfs archive, unknown type: $magic"
# The following are magic values for other compression formats
# but not added because not tested.
# TODO: open an issue for unsupported magic number reported on die.
#
#425a*) # bzip2
# DEBUG "archive segment $magic: bzip2"
# bunzip2 | unpack_cpio
#;;
#5d00*) # lzma
# DEBUG "archive segment $magic: lzma"
# unlzma | unpack_cpio
#;;
#894c*) # lzo
# DEBUG "archive segment $magic: lzo"
# lzop -d | unpack_cpio
#;;
#0221*) # lz4
# DEBUG "archive segment $magic: lz4"
# lz4 -d | unpack_cpio
# ;;
;;
esac
) <"$unpack_archive" >"$rest_archive"
orig_size="$(stat -c %s "$unpack_archive")"
rest_size="$(stat -c %s "$rest_archive")"
DEBUG "archive segment $magic: $((orig_size - rest_size)) bytes"
}
DEBUG "Unpacking $INITRAMFS_ARCHIVE to $DEST_DIR"
next_archive="$INITRAMFS_ARCHIVE"
rest_archive="/tmp/unpack_initramfs_rest"
# Break when there is no remaining data
while [ -s "$next_archive" ]; do
unpack_first_segment "$next_archive" "$DEST_DIR" "$rest_archive"
next_archive="/tmp/unpack_initramfs_next"
mv "$rest_archive" "$next_archive"
done